Skip to content

Commit 88ede3a

Browse files
committed
post: git cherry pick
1 parent f44dfee commit 88ede3a

File tree

29 files changed

+1761
-119
lines changed

29 files changed

+1761
-119
lines changed

content/posts/2025-12-01_python-asyncio-03.md

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,4 +440,157 @@ while True:
440440
然而,如果我们仅使用 selectors 来构建应用程序,就需要自行实现事件循环才能达到与 asyncio 相同的功能。
441441
下面介绍如何使用 async/await 来实现上面功能。
442442

443-
### An echo server on the asyncio event loop
443+
## An echo server on the asyncio event loop
444+
445+
使用 `select` 对于很多应用来说有些太底层了。
446+
我们可能希望在等待 socket 的时候,让代码在后台运行,或者我们可能希望按计划执行后台任务。
447+
如果只使用 selectors 来实现这个,我们将需要构建自己的事件循环,与此同时,asyncio 有一个完整的实现可以使用。
448+
此外,coroutines 和 tasks 在 selectors 之上提供了抽象层,这使得我们代码更易于实现和维护,无需考虑 selectors 细节。
449+
450+
下面通过 asyncio 的 coroutines 和 tasks 再次实现前面的 echo server。
451+
这里仍然会通过底层 API 来实现,这些 API 会返回 coroutines 和 tasks。
452+
453+
### Event loop coroutines for sockets
454+
455+
考虑到 sockets 的一个相对底层的概念,处理他们的方法是通过 asyncio 的事件循环。
456+
下面会使用三种主要的协程处理:
457+
458+
- `sock_accept`
459+
- `sock_recv`
460+
- `sock_sendall`
461+
462+
这些方法和之前的很类似,不同在于这些方法会将 socket 作为一个参数输入,这样我们可以 `await` 返回的协程,直到我们得到可以作用于其上的数据。
463+
464+
下面先从 `sock_accept` 开始,这个协程类似之前的 `socket.accept` 方法。
465+
466+
该方法返回一个 tuple: `(socket_connection, client_address)`,传入感兴趣的 socket,然后 `await` 等待连接返回。
467+
一但接受该协程就能获取到连接与地址,这个 socket 必须是非阻塞的,并和一个端口绑定起来:
468+
469+
```Python
470+
connection, addresss = await loop.socke_accept(socket)
471+
```
472+
473+
`sock_recv``sock_sendall` 也是类似的,输入一个 socket,然后 `await` 等待结果。
474+
475+
- `sock_recv` 会等待直到有可以处理的字节
476+
- `sock_sendall` 同时接受一个 socket 和要发送的 data,它会等待直到所有数据成功发送至 socket,并在成功后返回 `None`
477+
478+
```Python
479+
data = await loop.sock_recv(socket)
480+
success = await loop.sock_sendall(socket, data)
481+
```
482+
483+
### Designing an asyncio echo server
484+
485+
之前介绍了 coroutines 和 tasks,那么什么时候使用 coroutine,什么时候使用 task 呢?
486+
让我们来审视一下,我们希望应用程序如何表现以做出这一判断。
487+
488+
我们从如何监听应用连接开始。
489+
当监听应用连接的时候,一次将只能处理一个连接,因为 `socket.accept` 只会给客户端一个连接。
490+
如果有多个连接到达,后续的连接会被存储到一个被称作 `backlog` 的队列里面。
491+
492+
由于不需要并发处理多个连接,单个协程循环就足够了。
493+
这样能够让其他代码在等待连接的时候并发执行。
494+
这里定义一个一直循环的协程 `listen_for_connections`
495+
496+
```Python
497+
async def listen_for_connections(server_socket: socket, loop: AbstractEventLoop):
498+
while True:
499+
connection, address = await loop.sock_accept(server_socket)
500+
connection.setblocking(False)
501+
print(f"Got a connection from {address}")
502+
```
503+
504+
这样就有了一个监听连接的协程,由于要并发处理多个 connection,因此这里要为每个 connection 创建一个 task 来读写数据。
505+
506+
这里将创建负责处理数据的协程 `echo`,这个协程会一直循环来接收来自 client 的数据,一但其收到数据,就写会到 client 中去。
507+
然后在 `listen_for_connections` 里,创建一个 task 来包装 `echo` 协程。
508+
509+
```Python
510+
import asyncio
511+
import socket
512+
from asyncio import AbstractEventLoop
513+
514+
515+
async def echo(connection: socket, loop: AbstractEventLoop) -> None:
516+
while data := await loop.sock_recv(connection, 1024):
517+
await loop.sock_sendall(connection, data)
518+
519+
async def listen_for_connections(server_socket: socket, loop: AbstractEventLoop):
520+
while True:
521+
connection, address = await loop.sock_accept(server_socket)
522+
connection.setblocking(False)
523+
print(f"Got a connection from {address}")
524+
525+
asyncio.create_task(echo(connection, loop))
526+
527+
async def main():
528+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
529+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
530+
531+
server_address = ("127.0.0.1", 8000)
532+
server_socket.setblocking(False)
533+
server_socket.bind(server_address)
534+
server_socket.listen()
535+
536+
await listen_for_connections(server_socket, asyncio.get_event_loop())
537+
538+
asyncio.run(main())
539+
```
540+
541+
架构如下:
542+
543+
- 协程 `listen_for_connections` 持续监听连接,收到连接后该协程就会切换到 `echo` task 去处理每个连接
544+
- Client 1 echo task <--Read/Write--> Client 1
545+
- Client 2 echo task <--Read/Write--> Client 2
546+
- Client 3 echo task <--Read/Write--> Client 3
547+
548+
这样设计的 echo server 实际上有一个问题,下面来解决
549+
550+
### Handling errors in tasks
551+
552+
网络连接通常都是不可靠的,我们可能得到非预期的报错。
553+
下面修改 echo 的实现,添加一个错误处理的代码:
554+
555+
```Python
556+
async def echo(connection: socket, loop: AbstractEventLoop) -> None:
557+
while data := await loop.recv(connection, 1024):
558+
if data == b"boom\r\n":
559+
raise Exception("Unexcepted network error")
560+
await loop.sock_sendall(connection, data)
561+
```
562+
563+
现在只要发送 boom 就会导致下面这样的报错:
564+
565+
```text
566+
Got a connection from ('127.0.0.1', 49470)
567+
Task exception was never retrieved
568+
future: <Task finished name='Task-2' coro=<echo() done, defined at /Users/starslayerx/GitHub/book_asyncio/asyncio_echo_server.py:4> exception=Exception('Unexcepted network error')>
569+
Traceback (most recent call last):
570+
File "/Users/starslayerx/GitHub/book_asyncio/asyncio_echo_server.py", line 7, in echo
571+
raise Exception("Unexcepted network error")
572+
```
573+
574+
这里的重点在于 `Task exception was never retrieved`
575+
当一个异常在 task 内部被抛出时,这个任务会被视为已完成,并且它的“结果”就是这个异常。
576+
这意味着异常不会沿着调用栈向上传递。
577+
此外,这里没有任何清理逻辑。
578+
如果该异常被抛出,则无法对任务失败做出反应,因为从未获取 retrieve 这个异常。
579+
580+
要让异常真正传递,必须在 await 表达式中使用 task。
581+
当 await 一个失败的 task 时,异常会在 await 的地方重新抛出,其 traceback 也会在该位置体现。
582+
如果在程序中从未 await 一个 task,就有可能永远看不到这个 task 抛出的异常。
583+
584+
下面演示,与其忽略在 `listen_for_connection` 里面创建的 echo tasks,我们通过列表来跟踪他们
585+
586+
```Python
587+
tasks = []
588+
async def listen_for_connection(server_socket: socket, loop: AbstractEventLoop):
589+
while True:
590+
connection, address = await loop.socket_accept(server_socket)
591+
connection.setblocking(False)
592+
print(f"Got a connection from {address}")
593+
tasks.append(
594+
asyncio.create_task(echo(connection, loop))
595+
)
596+
```
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
+++
2+
date = '2025-12-08T8:00:00+08:00'
3+
draft = false
4+
title = 'Git cherry-pick'
5+
tags = ['Git']
6+
+++
7+
8+
### Patch Application
9+
10+
补丁应用类似下面这样(Codex 使用的 OpenAI Patch 是不是借鉴的这个?)的描述文件变更的文本文件,通常包含:
11+
12+
- 哪些文件被修改
13+
- 具体的行级变更
14+
- 上下文信息
15+
16+
```shell
17+
# 创建补丁
18+
git diff > changes.patch # 未暂存的修改
19+
git diff --cached > changes.patch # 已暂存的修改
20+
git format-patch HEAD~3 # 最近 3 次提交生成补丁
21+
22+
# 应用补丁
23+
git apply changes.patch # 直接应用,不创建提交
24+
git am changes.patch # 应用并创建提交(用于format-patch生成的)
25+
```
26+
27+
命令如 `git diff``git stach``git rebase` 都使用 patch。
28+
29+
这种修改方法,如果产生冲突则需要手动处理。
30+
31+
### 3-Way Merge
32+
33+
三方合并则是一种智能的合并算法,使用三个版本来解决合并冲突:
34+
35+
```text
36+
A - B (feature分支)
37+
/
38+
Base
39+
\
40+
C - D (main分支)
41+
```
42+
43+
1. Base: 共同的祖先提交
44+
2. Current: 当前分支的最新提交
45+
3. Incoming: 要合并进来的分支的最新提交
46+
47+
合并过程:
48+
49+
```bash
50+
git merge feature-branch
51+
git pull
52+
```
53+
54+
当同一行在两个分支都被修改时:
55+
56+
```
57+
<<<<<<< HEAD (Current分支)
58+
当前分支的内容
59+
=======
60+
要合并分支的内容
61+
>>>>>>> feature-branch
62+
```
63+
64+
像的 `git merge``git pull``git cherry-pick` 都使用的该算法
65+
66+
### Cherry-pick Guide
67+
68+
你可能认为 cherry-pick = `git show <commit> --patch` + `git apply`
69+
70+
但实际上,cherry-pick 使用三方合并机制,这解释了为什么它能在冲突时提供合并解决界面,而不仅仅是失败。
71+
72+
Git cherry-pick 用于将已有的提交应用到当前分支,它会提取指定提交引入的更改,然后创建一个包含这些修改的新提交。
73+
74+
- 选择性地应用特定提交(就像摘樱桃一样只挑选想要的)
75+
- 在不同分支间移动提交
76+
- 代码回滚或修复的重新应用
77+
- 跨分支的补丁应用
78+
79+
基本用法
80+
81+
```shell
82+
# 单个提交
83+
git cherry-pick <commit-hash>
84+
85+
# 多个提交
86+
git cherry-pick <commit1> <commit2> <comit3>
87+
88+
# 某个范围内的提交
89+
git cherry-pick start-commit^..end-commit
90+
```
91+
92+
常用选项
93+
94+
- `-e / --edit`: 在提交前编辑提交信息
95+
96+
```shell
97+
git cherry-pick -e <commit>
98+
```
99+
100+
- `-n / --no-commit`: 应用更改但不提交
101+
102+
```shell
103+
git cherry-pick -n <commit1> <commit2>
104+
# 继续修改,然后提交
105+
git commit -m "Combined changes"
106+
```
107+
108+
- `-x`: 在提交中添加来源信息
109+
110+
```shell
111+
git cherry-pick -x <commit>
112+
```
113+
114+
- `-s / --signoff`: 在提交信息末尾添加 Signed-off-by 签名
115+
116+
```shell
117+
git cherry-pick -s <commit>
118+
```
119+
120+
- `--ff`: 如果可能,执行快速前进合并
121+
122+
```
123+
git cherry-pick --ff <commit>
124+
```
125+
126+
- `-m <parent-number>`: 处理合并提交时指定主分支
127+
128+
```shell
129+
# 对于合并提交,需要指定使用哪个父提交
130+
git cherry-pick -m 1 <merge-commit>
131+
```

public/archives/index.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@
5151
<span class="max-w-[4rem] md:max-w-none truncate">Home</span></a></li><li class="flex items-center gap-1 md:gap-2 min-w-0"><span class="text-muted-foreground/50 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
5252
</span><span class="text-foreground flex items-center gap-0.5 md:gap-1 font-medium min-w-0 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
5353
<span class="max-w-[3rem] md:max-w-none truncate">Archives</span></span></li></ol></nav><header class=mb-8><div class="mb-4 flex items-center gap-3"><svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg><h1 class="text-foreground text-3xl font-bold">Archives</h1></div><p class="text-muted-foreground mb-6">Browse all articles in chronological order and discover what interests you.</p><div class="text-muted-foreground flex items-center gap-4 text-sm"><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
54-
<span>83 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55-
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">81
56-
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">December 2025</h3><p class="text-muted-foreground text-xs">1
57-
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/writing-a-good-claude.md/ class=block>Writing a good CLAUDE.md</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
54+
<span>84 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55+
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">82
56+
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">December 2025</h3><p class="text-muted-foreground text-xs">2
57+
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/git-cherry-pick/ class=block>Git cherry-pick</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
58+
<time datetime=2025-12-08>12-08</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
59+
<span>2
60+
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/writing-a-good-claude.md/ class=block>Writing a good CLAUDE.md</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
5861
<time datetime=2025-12-02>12-02</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
5962
<span>7
6063
min</span></div></div></div></div></article></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">November 2025</h3><p class="text-muted-foreground text-xs">21

0 commit comments

Comments
 (0)