Skip to content

Commit 77a04fe

Browse files
committed
Merge remote-tracking branch 'upstream/master' into bench-bench
2 parents a4ff2e4 + f01620e commit 77a04fe

21 files changed

+1054
-611
lines changed
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
name: Backport
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- closed
7+
workflow_dispatch:
8+
inputs:
9+
pr_number:
10+
description: 'PR number'
11+
required: true
12+
type: string
13+
target_branch:
14+
description: 'Target branch'
15+
required: true
16+
type: string
17+
18+
concurrency:
19+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr_number || github.run_id }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
backport:
24+
timeout-minutes: 30
25+
if: >
26+
github.event_name == 'workflow_dispatch' ||
27+
(github.event_name == 'pull_request_target' &&
28+
github.event.pull_request.merged == true &&
29+
((github.event.action == 'closed') ||
30+
(github.event.action == 'labeled' && startsWith(github.event.label.name, 'backport-to-'))))
31+
permissions:
32+
contents: write
33+
pull-requests: write
34+
runs-on: ubuntu-latest
35+
steps:
36+
- name: Checkout
37+
uses: actions/checkout@v4
38+
with:
39+
fetch-depth: 0
40+
41+
- name: Configure Git
42+
run: |
43+
git config user.name "github-actions[bot]"
44+
git config user.email "github-actions[bot]@users.noreply.github.com"
45+
46+
- name: Execute Backport
47+
id: execute-backport
48+
env:
49+
GH_TOKEN: ${{ secrets.PYMILVUS_BOT_TOKEN }}
50+
EVENT_NAME: ${{ github.event_name }}
51+
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
52+
INPUT_TARGET_BRANCH: ${{ inputs.target_branch }}
53+
TRIGGER_USER: ${{ github.actor }}
54+
run: |
55+
# Helper function to write summary
56+
write_summary() {
57+
local status="$1"
58+
local title="$2"
59+
local details="$3"
60+
echo "## $status $title" >> $GITHUB_STEP_SUMMARY
61+
echo "" >> $GITHUB_STEP_SUMMARY
62+
echo "$details" >> $GITHUB_STEP_SUMMARY
63+
echo "" >> $GITHUB_STEP_SUMMARY
64+
}
65+
66+
if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
67+
PR_NUMBER="$INPUT_PR_NUMBER"
68+
IS_MANUAL="true"
69+
70+
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
71+
echo "::error::Invalid PR number: $PR_NUMBER (must be a positive integer)"
72+
write_summary "❌" "Backport Failed" "**Reason:** Invalid PR number \`$PR_NUMBER\` (must be a positive integer)"
73+
exit 1
74+
fi
75+
76+
if ! [[ "$INPUT_TARGET_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then
77+
echo "::error::Invalid branch name: $INPUT_TARGET_BRANCH (contains invalid characters)"
78+
write_summary "❌" "Backport Failed" "**Reason:** Invalid branch name \`$INPUT_TARGET_BRANCH\` (contains invalid characters)"
79+
exit 1
80+
fi
81+
else
82+
PR_NUMBER="${{ github.event.pull_request.number }}"
83+
IS_MANUAL="false"
84+
fi
85+
86+
echo "Starting Backport for PR #$PR_NUMBER (Triggered by $TRIGGER_USER)"
87+
88+
if ! PR_DATA=$(gh pr view "$PR_NUMBER" --json mergeCommit,title,files,author,state,labels,body --jq . 2>&1); then
89+
echo "::error::Failed to fetch PR data: $PR_DATA"
90+
write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Reason:** Failed to fetch PR data\n\n**Error:** \`$PR_DATA\`"
91+
exit 1
92+
fi
93+
94+
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid // empty')
95+
PR_TITLE=$(echo "$PR_DATA" | jq -r .title)
96+
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login // "ghost"')
97+
PR_STATE=$(echo "$PR_DATA" | jq -r .state)
98+
CHANGED_FILES=$(echo "$PR_DATA" | jq -r '.files[].path // empty')
99+
100+
if [ -z "$PR_AUTHOR" ] || [ "$PR_AUTHOR" == "null" ]; then
101+
PR_AUTHOR="ghost"
102+
fi
103+
104+
PR_TITLE_CLEAN=$(echo "$PR_TITLE" | tr -d '\n\r' | sed 's/["`\\]/ /g' | head -c 200)
105+
if [ -z "$PR_TITLE_CLEAN" ]; then
106+
PR_TITLE_CLEAN="Backport PR #$PR_NUMBER"
107+
fi
108+
109+
if [ "$PR_STATE" != "MERGED" ]; then
110+
echo "::error::PR is not merged."
111+
write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Reason:** PR is not merged yet"
112+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', PR #'"$PR_NUMBER"$' is not merged yet.\n\n(cc @'"$TRIGGER_USER"$')'
113+
exit 0
114+
fi
115+
116+
if [ -z "$MERGE_COMMIT" ] || [ "$MERGE_COMMIT" == "null" ]; then
117+
echo "::error::Cannot find merge commit for PR #$PR_NUMBER"
118+
write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Reason:** Cannot determine merge commit for this PR"
119+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', cannot determine merge commit for this PR.\n\n(cc @'"$TRIGGER_USER"$')'
120+
exit 0
121+
fi
122+
123+
if [ -n "$CHANGED_FILES" ] && echo "$CHANGED_FILES" | grep -q "^pymilvus/grpc_gen/"; then
124+
echo "::notice::Skipping restricted paths."
125+
write_summary "⚠️" "Backport Skipped" "**PR:** #$PR_NUMBER\n\n**Reason:** This PR modifies \`pymilvus/grpc_gen/\`. Please backport manually."
126+
gh pr comment "$PR_NUMBER" --body $'⚠️ **Backport Skipped**\nHi @'"$PR_AUTHOR"$', this PR modifies `pymilvus/grpc_gen/`. Please backport manually.\n\n(cc @'"$TRIGGER_USER"$')'
127+
exit 0
128+
fi
129+
130+
TARGET_BRANCHES=""
131+
LABELED_BRANCHES=$(echo "$PR_DATA" | jq -r '.labels[].name' | grep '^backport-to-' | sed 's/^backport-to-//' || true)
132+
if [ "$IS_MANUAL" == "true" ]; then
133+
# Check if the target branch has corresponding label
134+
if echo "$LABELED_BRANCHES" | grep -qx "$INPUT_TARGET_BRANCH"; then
135+
TARGET_BRANCHES="$INPUT_TARGET_BRANCH"
136+
else
137+
echo "::error::Target branch '$INPUT_TARGET_BRANCH' is not labeled with 'backport-to-$INPUT_TARGET_BRANCH'"
138+
write_summary "❌" "Backport Failed" "**PR:** #$PR_NUMBER\n\n**Target Branch:** \`$INPUT_TARGET_BRANCH\`\n\n**Reason:** Missing label \`backport-to-$INPUT_TARGET_BRANCH\`\n\n**Available Labels:** \`$LABELED_BRANCHES\`"
139+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', target branch `'"$INPUT_TARGET_BRANCH"$'` is not in the backport labels. Please add label `backport-to-'"$INPUT_TARGET_BRANCH"$'` first.\n\n(cc @'"$TRIGGER_USER"$')'
140+
exit 0
141+
fi
142+
else
143+
TARGET_BRANCHES="$LABELED_BRANCHES"
144+
fi
145+
146+
if [ -z "$TARGET_BRANCHES" ]; then
147+
echo "No target branches found."
148+
write_summary "ℹ️" "No Backport Needed" "**PR:** #$PR_NUMBER\n\n**Reason:** No \`backport-to-*\` labels found on this PR"
149+
exit 0
150+
fi
151+
152+
BACKPORT_SUCCESS="false"
153+
BACKPORT_COUNT=0
154+
SUMMARY_RESULTS=""
155+
156+
while IFS= read -r TARGET_BRANCH; do
157+
[ -z "$TARGET_BRANCH" ] && continue
158+
TARGET_BRANCH=$(echo "$TARGET_BRANCH" | xargs)
159+
160+
echo "::group::Processing backport to $TARGET_BRANCH"
161+
162+
if ! git ls-remote --exit-code --heads origin "$TARGET_BRANCH" > /dev/null 2>&1; then
163+
echo "::error::Target branch '$TARGET_BRANCH' does not exist."
164+
SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Branch does not exist |"
165+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', target branch `'"$TARGET_BRANCH"$'` does not exist.\n\n(cc @'"$TRIGGER_USER"$')'
166+
echo "::endgroup::"
167+
continue
168+
fi
169+
170+
echo "Checking for existing backport PR..."
171+
EXISTING_PR=$(gh pr list \
172+
--base "$TARGET_BRANCH" \
173+
--state all \
174+
--search "in:title [Backport $TARGET_BRANCH]" \
175+
--json number,title,state,mergedAt \
176+
--jq ".[] | select(.title | contains(\"#$PR_NUMBER\")) | select(.state == \"OPEN\" or .mergedAt != null) | .number" \
177+
| head -n 1)
178+
179+
if [ -n "$EXISTING_PR" ]; then
180+
echo "::notice::Backport PR already exists: #$EXISTING_PR"
181+
SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ℹ️ Skipped | Already exists: #$EXISTING_PR |"
182+
gh pr comment "$PR_NUMBER" --body $'ℹ️ **Backport Already Exists**\nHi @'"$PR_AUTHOR"$', a backport PR for `'"$TARGET_BRANCH"$'` already exists: #'"$EXISTING_PR"$'\n\n(cc @'"$TRIGGER_USER"$')'
183+
echo "::endgroup::"
184+
continue
185+
fi
186+
187+
git reset --hard HEAD
188+
git clean -fdx
189+
190+
TARGET_BRANCH_SHORT="${TARGET_BRANCH:0:100}"
191+
BRANCH_SUFFIX="$(date +%s)-$RANDOM"
192+
BACKPORT_BRANCH="backport-$PR_NUMBER-to-$TARGET_BRANCH_SHORT-$BRANCH_SUFFIX"
193+
BACKPORT_BRANCH="${BACKPORT_BRANCH:0:250}"
194+
195+
if ! git fetch origin "$TARGET_BRANCH"; then
196+
echo "::error::Failed to fetch $TARGET_BRANCH"
197+
SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Failed to fetch branch |"
198+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', failed to fetch target branch `'"$TARGET_BRANCH"$'`.\n\n(cc @'"$TRIGGER_USER"$')'
199+
echo "::endgroup::"
200+
continue
201+
fi
202+
203+
git checkout -b "$BACKPORT_BRANCH" "origin/$TARGET_BRANCH"
204+
205+
echo "Attempting cherry-pick of $MERGE_COMMIT..."
206+
207+
if git cherry-pick -x -s "$MERGE_COMMIT" 2>&1; then
208+
echo "Cherry-pick successful."
209+
else
210+
echo "Standard cherry-pick failed, trying with -m 1..."
211+
git cherry-pick --abort 2>/dev/null || true
212+
213+
if git cherry-pick -x -s -m 1 "$MERGE_COMMIT" 2>&1; then
214+
echo "Cherry-pick with -m 1 successful."
215+
else
216+
echo "::error::Cherry-pick failed due to conflicts."
217+
git cherry-pick --abort 2>/dev/null || true
218+
219+
SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Merge conflicts - manual backport required |"
220+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', I could not cherry-pick this to `'"$TARGET_BRANCH"$'` due to **merge conflicts**. Please backport manually.\n\n(cc @'"$TRIGGER_USER"$')'
221+
echo "::endgroup::"
222+
continue
223+
fi
224+
fi
225+
226+
if ! git push origin "$BACKPORT_BRANCH"; then
227+
echo "::error::Failed to push branch $BACKPORT_BRANCH"
228+
SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Failed to push branch |"
229+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport Failed**\nHi @'"$PR_AUTHOR"$', failed to push backport branch for `'"$TARGET_BRANCH"$'`.\n\n(cc @'"$TRIGGER_USER"$')'
230+
echo "::endgroup::"
231+
continue
232+
fi
233+
234+
PR_BODY="Backport of #$PR_NUMBER to \`$TARGET_BRANCH\`."
235+
ASSIGNEE_ARG=""
236+
if [ "$IS_MANUAL" == "true" ]; then
237+
PR_BODY="Manual backport of #$PR_NUMBER to \`$TARGET_BRANCH\`."
238+
ASSIGNEE_ARG="--assignee $TRIGGER_USER"
239+
fi
240+
241+
if NEW_PR_URL=$(gh pr create \
242+
--base "$TARGET_BRANCH" \
243+
--head "$BACKPORT_BRANCH" \
244+
--title "[Backport $TARGET_BRANCH] $PR_TITLE_CLEAN (#$PR_NUMBER)" \
245+
--body "$PR_BODY" \
246+
--label "backport" \
247+
$ASSIGNEE_ARG 2>&1); then
248+
249+
gh pr comment "$PR_NUMBER" --body $'✅ **Backport Created**\nHi @'"$PR_AUTHOR"$', Backport PR for `'"$TARGET_BRANCH"$'` has been created: '"$NEW_PR_URL"$'\n\n(cc @'"$TRIGGER_USER"$')'
250+
251+
echo "Backport PR created: $NEW_PR_URL"
252+
SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ✅ Success | $NEW_PR_URL |"
253+
BACKPORT_SUCCESS="true"
254+
BACKPORT_COUNT=$((BACKPORT_COUNT + 1))
255+
else
256+
echo "::error::Failed to create backport PR."
257+
SUMMARY_RESULTS="${SUMMARY_RESULTS}\n| \`$TARGET_BRANCH\` | ❌ Failed | Failed to create PR |"
258+
git push origin --delete "$BACKPORT_BRANCH" 2>/dev/null || true
259+
gh pr comment "$PR_NUMBER" --body $'❌ **Backport PR Creation Failed**\nHi @'"$PR_AUTHOR"$', Failed to create backport PR for `'"$TARGET_BRANCH"$'`.\n\nError: '"$NEW_PR_URL"$'\n\n(cc @'"$TRIGGER_USER"$')'
260+
fi
261+
262+
echo "::endgroup::"
263+
done < <(echo "$TARGET_BRANCHES")
264+
265+
# Write final summary
266+
if [ "$BACKPORT_SUCCESS" == "true" ]; then
267+
echo "## ✅ Backport Completed" >> $GITHUB_STEP_SUMMARY
268+
else
269+
echo "## ❌ Backport Failed" >> $GITHUB_STEP_SUMMARY
270+
fi
271+
echo "" >> $GITHUB_STEP_SUMMARY
272+
echo "**PR:** #$PR_NUMBER" >> $GITHUB_STEP_SUMMARY
273+
echo "**Title:** $PR_TITLE_CLEAN" >> $GITHUB_STEP_SUMMARY
274+
echo "**Triggered by:** @$TRIGGER_USER" >> $GITHUB_STEP_SUMMARY
275+
echo "" >> $GITHUB_STEP_SUMMARY
276+
echo "### Results" >> $GITHUB_STEP_SUMMARY
277+
echo "" >> $GITHUB_STEP_SUMMARY
278+
echo "| Branch | Status | Details |" >> $GITHUB_STEP_SUMMARY
279+
echo "|--------|--------|---------|" >> $GITHUB_STEP_SUMMARY
280+
echo -e "$SUMMARY_RESULTS" >> $GITHUB_STEP_SUMMARY
281+
282+
echo "success=$BACKPORT_SUCCESS" >> $GITHUB_OUTPUT
283+
echo "count=$BACKPORT_COUNT" >> $GITHUB_OUTPUT
284+
echo "Backport process completed. Success: $BACKPORT_SUCCESS, Count: $BACKPORT_COUNT"
285+
286+
- name: Label Original PR
287+
if: steps.execute-backport.outputs.success == 'true'
288+
env:
289+
GH_TOKEN: ${{ secrets.PYMILVUS_BOT_TOKEN }}
290+
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
291+
EVENT_NAME: ${{ github.event_name }}
292+
run: |
293+
if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
294+
PR_NUMBER="$INPUT_PR_NUMBER"
295+
else
296+
PR_NUMBER="${{ github.event.pull_request.number }}"
297+
fi
298+
gh pr edit "$PR_NUMBER" --add-label "backported" || echo "::warning::Failed to add 'backported' label"

CONTRIBUTING.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ Note: the problems, features, and questions mentioned here are not limited to Py
6767

6868
`setup.py`: Package script for PyMilvus.
6969

70+
## Backporting (Cherry-pick)
71+
72+
We use a bot to automate backporting bug fixes to branches.
73+
74+
**How to use:**
75+
Simply add a label `backport-to-<branch-name>` to your Pull Request (e.g., `backport-to-2.6`).
76+
***Success**: The bot will create a new backport PR automatically.
77+
***Failure**: The bot will comment on your PR if there are conflicts or restricted files (`proto_gen/`).
78+
79+
If the bot fails due to conflicts, please backport manually.
80+
7081
## Congratulations! You are now the contributor to the Milvus community!
7182

7283
Apart from dealing with codes and machines, you are always welcome to communicate with any member from the Milvus community. New faces join us every day, and they may as well encounter the same challenges as you faced beore. Feel free to help them. You can pass on the collaborative spirit from the assistance you acquired when you first joined the community. Let us build a collaborative, open-source, exuberant, and tolerant community together!

CONTRIBUTING_CN.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ PyMilvus 的 Github issue 列表中,打上了 [good-first-issue](https://githu
9191

9292
### 通过所有 Github Actions
9393

94+
## Cherry-pick
95+
96+
使用方法: 只需为你的 Pull Request 添加 backport-to-<branch-name> 格式的标签即可(例如:backport-to-2.6)。
97+
98+
✅ 成功:机器人会自动创建一个新的 Backport PR。
99+
100+
❌ 失败:如果存在代码冲突或修改了受限文件(如 proto_gen/),机器人会在 PR 中留言提醒。
101+
102+
如果机器人因冲突执行失败,请手动进行 Backport 操作。
103+
94104
## 恭喜你!你已经成为了 Milvus 社区的贡献者!
95105

96106
除了和代码、机器打交道,你还可以和 Milvus 社区中的人交流。社区中每天都有很多新面孔加入,当他们遇到的困难正好是你所了解的地方,请尽情的帮助这些人。回想你初次接触 Milvus 接受过的帮助,你也可以将这样的交流互助精神不断传递下去,我们一起共创一个协作、开源、开放、包容的社区。

pymilvus/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@
4545
from .orm.index import Index
4646
from .orm.partition import Partition
4747
from .orm.role import Role
48-
from .orm.schema import CollectionSchema, FieldSchema, Function, FunctionScore, StructFieldSchema
48+
from .orm.schema import (
49+
CollectionSchema,
50+
FieldSchema,
51+
Function,
52+
FunctionScore,
53+
LexicalHighlighter,
54+
StructFieldSchema,
55+
)
4956
from .orm.utility import (
5057
create_resource_group,
5158
create_user,
@@ -96,6 +103,7 @@
96103
"Hits",
97104
"Index",
98105
"IndexType",
106+
"LexicalHighlighter",
99107
"MilvusClient",
100108
"MilvusException",
101109
"MilvusUnavailableException",

pymilvus/client/async_grpc_handler.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
from pymilvus.grpc_gen import common_pb2, milvus_pb2_grpc
2525
from pymilvus.grpc_gen import milvus_pb2 as milvus_types
26-
from pymilvus.orm.schema import Function
26+
from pymilvus.orm.schema import Function, Highlighter
2727
from pymilvus.settings import Config
2828

2929
from . import entity_helper, ts_utils, utils
@@ -830,6 +830,7 @@ async def search(
830830
round_decimal: int = -1,
831831
timeout: Optional[float] = None,
832832
ranker: Optional[Function] = None,
833+
highlighter: Optional[Highlighter] = None,
833834
**kwargs,
834835
):
835836
await self.ensure_channel_ready()
@@ -860,6 +861,7 @@ async def search(
860861
output_fields,
861862
round_decimal,
862863
ranker=ranker,
864+
highlighter=highlighter,
863865
**kwargs,
864866
)
865867
return await self._execute_search(request, timeout, round_decimal=round_decimal, **kwargs)

0 commit comments

Comments
 (0)