Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Apache RocketMQ
Rocketmq
提交
b70b6800
R
Rocketmq
项目概览
Apache RocketMQ
/
Rocketmq
上一次同步 大约 3 年
通知
267
Star
16139
Fork
68
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
Rocketmq
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
b70b6800
编写于
6月 29, 2017
作者:
Y
yukon
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[ROCKETMQ-236] Script to merge github pull request
上级
323eb5ce
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
449 addition
and
0 deletion
+449
-0
dev/merge_rocketmq_pr.py
dev/merge_rocketmq_pr.py
+449
-0
未找到文件。
dev/merge_rocketmq_pr.py
0 → 100644
浏览文件 @
b70b6800
#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This script is a modified version of the one created by the Spark
# project (https://github.com/apache/spark/blob/master/dev/merge_spark_pr.py).
# Utility for creating well-formed pull request merges and pushing them to Apache.
# usage: ./merge_rocketmq_pr.py (see config env vars below)
#
# This utility assumes you already have local a RocketMQ git folder and that you
# have added remotes corresponding to both (i) the github apache RocketMQ
# mirror and (ii) the apache git repo.
import
json
import
os
import
re
import
subprocess
import
sys
import
urllib2
try
:
import
jira.client
JIRA_IMPORTED
=
True
except
ImportError
:
JIRA_IMPORTED
=
False
# Location of your RocketMQ git development area
ROCKETMQ_HOME
=
os
.
environ
.
get
(
"ROCKETMQ_HOME"
,
os
.
getcwd
())
# Remote name which points to the Gihub site
PR_REMOTE_NAME
=
os
.
environ
.
get
(
"PR_REMOTE_NAME"
,
"apache-github"
)
# Remote name which points to Apache git
PUSH_REMOTE_NAME
=
os
.
environ
.
get
(
"PUSH_REMOTE_NAME"
,
"origin"
)
# ASF JIRA username
JIRA_USERNAME
=
os
.
environ
.
get
(
"JIRA_USERNAME"
,
""
)
# ASF JIRA password
JIRA_PASSWORD
=
os
.
environ
.
get
(
"JIRA_PASSWORD"
,
""
)
# OAuth key used for issuing requests against the GitHub API. If this is not defined, then requests
# will be unauthenticated. You should only need to configure this if you find yourself regularly
# exceeding your IP's unauthenticated request rate limit. You can create an OAuth key at
# https://github.com/settings/tokens. This script only requires the "public_repo" scope.
GITHUB_OAUTH_KEY
=
os
.
environ
.
get
(
"GITHUB_OAUTH_KEY"
)
GITHUB_BASE
=
"https://github.com/apache/incubator-rocketmq/pull"
GITHUB_API_BASE
=
"https://api.github.com/repos/apache/incubator-rocketmq"
JIRA_BASE
=
"https://issues.apache.org/jira/browse"
JIRA_API_BASE
=
"https://issues.apache.org/jira"
# Prefix added to temporary branches
BRANCH_PREFIX
=
"PR_TOOL"
DEVELOP_BRANCH
=
"develop"
def
get_json
(
url
):
try
:
request
=
urllib2
.
Request
(
url
)
if
GITHUB_OAUTH_KEY
:
request
.
add_header
(
'Authorization'
,
'token %s'
%
GITHUB_OAUTH_KEY
)
return
json
.
load
(
urllib2
.
urlopen
(
request
))
except
urllib2
.
HTTPError
as
e
:
if
"X-RateLimit-Remaining"
in
e
.
headers
and
e
.
headers
[
"X-RateLimit-Remaining"
]
==
'0'
:
print
(
"Exceeded the GitHub API rate limit; see the instructions in "
+
"dev/merge_rocketmq_pr.py to configure an OAuth token for making authenticated "
+
"GitHub requests."
)
else
:
print
(
"Unable to fetch URL, exiting: %s"
%
url
)
sys
.
exit
(
-
1
)
def
fail
(
msg
):
print
(
msg
)
clean_up
()
sys
.
exit
(
-
1
)
def
run_cmd
(
cmd
):
print
(
cmd
)
if
isinstance
(
cmd
,
list
):
return
subprocess
.
check_output
(
cmd
)
else
:
return
subprocess
.
check_output
(
cmd
.
split
(
" "
))
def
continue_maybe
(
prompt
):
result
=
raw_input
(
"
\n
%s (y/n): "
%
prompt
)
if
result
.
lower
()
!=
"y"
:
fail
(
"Okay, exiting"
)
def
clean_up
():
print
(
"Restoring head pointer to %s"
%
original_head
)
run_cmd
(
"git checkout %s"
%
original_head
)
branches
=
run_cmd
(
"git branch"
).
replace
(
" "
,
""
).
split
(
"
\n
"
)
for
branch
in
filter
(
lambda
x
:
x
.
startswith
(
BRANCH_PREFIX
),
branches
):
print
(
"Deleting local branch %s"
%
branch
)
run_cmd
(
"git branch -D %s"
%
branch
)
# merge the requested PR and return the merge hash
def
merge_pr
(
pr_num
,
target_ref
,
title
,
body
,
pr_repo_desc
):
pr_branch_name
=
"%s_MERGE_PR_%s"
%
(
BRANCH_PREFIX
,
pr_num
)
target_branch_name
=
"%s_MERGE_PR_%s_%s"
%
(
BRANCH_PREFIX
,
pr_num
,
target_ref
.
upper
())
run_cmd
(
"git fetch %s pull/%s/head:%s"
%
(
PR_REMOTE_NAME
,
pr_num
,
pr_branch_name
))
run_cmd
(
"git fetch %s %s:%s"
%
(
PUSH_REMOTE_NAME
,
target_ref
,
target_branch_name
))
run_cmd
(
"git checkout %s"
%
target_branch_name
)
had_conflicts
=
False
try
:
run_cmd
([
'git'
,
'merge'
,
pr_branch_name
,
'--squash'
])
except
Exception
as
e
:
msg
=
"Error merging: %s
\n
Would you like to manually fix-up this merge?"
%
e
continue_maybe
(
msg
)
msg
=
"Okay, please fix any conflicts and 'git add' conflicting files... Finished?"
continue_maybe
(
msg
)
had_conflicts
=
True
commit_authors
=
run_cmd
([
'git'
,
'log'
,
'HEAD..%s'
%
pr_branch_name
,
'--pretty=format:%an <%ae>'
]).
split
(
"
\n
"
)
distinct_authors
=
sorted
(
set
(
commit_authors
),
key
=
lambda
x
:
commit_authors
.
count
(
x
),
reverse
=
True
)
primary_author
=
raw_input
(
"Enter primary author in the format of
\"
name <email>
\"
[%s]: "
%
distinct_authors
[
0
])
if
primary_author
==
""
:
primary_author
=
distinct_authors
[
0
]
commits
=
run_cmd
([
'git'
,
'log'
,
'HEAD..%s'
%
pr_branch_name
,
'--pretty=format:%h [%an] %s'
]).
split
(
"
\n\n
"
)
merge_message_flags
=
[]
title
=
raw_input
(
"Modify commit log [%s]: "
%
title
)
merge_message_flags
+=
[
"-m"
,
title
]
authors
=
"
\n
"
.
join
([
"Author: %s"
%
a
for
a
in
distinct_authors
])
merge_message_flags
+=
[
"-m"
,
authors
]
if
had_conflicts
:
committer_name
=
run_cmd
(
"git config --get user.name"
).
strip
()
committer_email
=
run_cmd
(
"git config --get user.email"
).
strip
()
message
=
"This patch had conflicts when merged, resolved by
\n
Committer: %s <%s>"
%
(
committer_name
,
committer_email
)
merge_message_flags
+=
[
"-m"
,
message
]
# The string "Closes #%s" string is required for GitHub to correctly close the PR
merge_message_flags
+=
[
"-m"
,
"Closes #%s from %s."
%
(
pr_num
,
pr_repo_desc
)]
run_cmd
([
'git'
,
'commit'
,
'--author="%s"'
%
primary_author
]
+
merge_message_flags
)
continue_maybe
(
"Merge complete (local ref %s). Push to %s?"
%
(
target_branch_name
,
PUSH_REMOTE_NAME
))
try
:
run_cmd
(
'git push %s %s:%s'
%
(
PUSH_REMOTE_NAME
,
target_branch_name
,
target_ref
))
except
Exception
as
e
:
clean_up
()
fail
(
"Exception while pushing: %s"
%
e
)
merge_hash
=
run_cmd
(
"git rev-parse %s"
%
target_branch_name
)[:
8
]
clean_up
()
print
(
"Pull request #%s merged!"
%
pr_num
)
print
(
"Merge hash: %s"
%
merge_hash
)
return
merge_hash
def
cherry_pick
(
pr_num
,
merge_hash
,
default_branch
):
pick_ref
=
raw_input
(
"Enter a branch name [%s]: "
%
default_branch
)
if
pick_ref
==
""
:
pick_ref
=
default_branch
pick_branch_name
=
"%s_PICK_PR_%s_%s"
%
(
BRANCH_PREFIX
,
pr_num
,
pick_ref
.
upper
())
run_cmd
(
"git fetch %s %s:%s"
%
(
PUSH_REMOTE_NAME
,
pick_ref
,
pick_branch_name
))
run_cmd
(
"git checkout %s"
%
pick_branch_name
)
try
:
run_cmd
(
"git cherry-pick -sx %s"
%
merge_hash
)
except
Exception
as
e
:
msg
=
"Error cherry-picking: %s
\n
Would you like to manually fix-up this merge?"
%
e
continue_maybe
(
msg
)
msg
=
"Okay, please fix any conflicts and finish the cherry-pick. Finished?"
continue_maybe
(
msg
)
continue_maybe
(
"Pick complete (local ref %s). Push to %s?"
%
(
pick_branch_name
,
PUSH_REMOTE_NAME
))
try
:
run_cmd
(
'git push %s %s:%s'
%
(
PUSH_REMOTE_NAME
,
pick_branch_name
,
pick_ref
))
except
Exception
as
e
:
clean_up
()
fail
(
"Exception while pushing: %s"
%
e
)
pick_hash
=
run_cmd
(
"git rev-parse %s"
%
pick_branch_name
)[:
8
]
clean_up
()
print
(
"Pull request #%s picked into %s!"
%
(
pr_num
,
pick_ref
))
print
(
"Pick hash: %s"
%
pick_hash
)
return
pick_ref
def
fix_version_from_branch
(
branch
,
versions
):
# Note: Assumes this is a sorted (newest->oldest) list of un-released versions
if
branch
==
"master"
:
return
versions
[
0
]
else
:
branch_ver
=
branch
.
replace
(
"branch-"
,
""
)
return
filter
(
lambda
x
:
x
.
name
.
startswith
(
branch_ver
),
versions
)[
-
1
]
def
resolve_jira_issue
(
merge_branches
,
comment
,
default_jira_id
=
""
):
asf_jira
=
jira
.
client
.
JIRA
({
'server'
:
JIRA_API_BASE
},
basic_auth
=
(
JIRA_USERNAME
,
JIRA_PASSWORD
))
jira_id
=
raw_input
(
"Enter a JIRA id [%s]: "
%
default_jira_id
)
if
jira_id
==
""
:
jira_id
=
default_jira_id
try
:
issue
=
asf_jira
.
issue
(
jira_id
)
except
Exception
as
e
:
fail
(
"ASF JIRA could not find %s
\n
%s"
%
(
jira_id
,
e
))
cur_status
=
issue
.
fields
.
status
.
name
cur_summary
=
issue
.
fields
.
summary
cur_assignee
=
issue
.
fields
.
assignee
if
cur_assignee
is
None
:
cur_assignee
=
"NOT ASSIGNED!!!"
else
:
cur_assignee
=
cur_assignee
.
displayName
if
cur_status
==
"Resolved"
or
cur_status
==
"Closed"
:
fail
(
"JIRA issue %s already has status '%s'"
%
(
jira_id
,
cur_status
))
print
(
"=== JIRA %s ==="
%
jira_id
)
print
(
"summary
\t\t
%s
\n
assignee
\t
%s
\n
status
\t\t
%s
\n
url
\t\t
%s/%s
\n
"
%
(
cur_summary
,
cur_assignee
,
cur_status
,
JIRA_BASE
,
jira_id
))
versions
=
asf_jira
.
project_versions
(
"ROCKETMQ"
)
versions
=
sorted
(
versions
,
key
=
lambda
x
:
x
.
name
,
reverse
=
True
)
versions
=
filter
(
lambda
x
:
x
.
raw
[
'released'
]
is
False
,
versions
)
# Consider only x.y.z versions
versions
=
filter
(
lambda
x
:
re
.
match
(
'\d+\.\d+\.\d+'
,
x
.
name
),
versions
)
default_fix_versions
=
map
(
lambda
x
:
fix_version_from_branch
(
x
,
versions
).
name
,
merge_branches
)
for
v
in
default_fix_versions
:
# Handles the case where we have forked a release branch but not yet made the release.
# In this case, if the PR is committed to the master branch and the release branch, we
# only consider the release branch to be the fix version. E.g. it is not valid to have
# both 1.1.0 and 1.0.0 as fix versions.
(
major
,
minor
,
patch
)
=
v
.
split
(
"."
)
if
patch
==
"0"
:
previous
=
"%s.%s.%s"
%
(
major
,
int
(
minor
)
-
1
,
0
)
if
previous
in
default_fix_versions
:
default_fix_versions
=
filter
(
lambda
x
:
x
!=
v
,
default_fix_versions
)
default_fix_versions
=
","
.
join
(
default_fix_versions
)
fix_versions
=
raw_input
(
"Enter comma-separated fix version(s) [%s]: "
%
default_fix_versions
)
if
fix_versions
==
""
:
fix_versions
=
default_fix_versions
fix_versions
=
fix_versions
.
replace
(
" "
,
""
).
split
(
","
)
def
get_version_json
(
version_str
):
return
filter
(
lambda
v
:
v
.
name
==
version_str
,
versions
)[
0
].
raw
jira_fix_versions
=
map
(
lambda
v
:
get_version_json
(
v
),
fix_versions
)
resolve
=
filter
(
lambda
a
:
a
[
'name'
]
==
"Resolve Issue"
,
asf_jira
.
transitions
(
jira_id
))[
0
]
resolution
=
filter
(
lambda
r
:
r
.
raw
[
'name'
]
==
"Fixed"
,
asf_jira
.
resolutions
())[
0
]
asf_jira
.
transition_issue
(
jira_id
,
resolve
[
"id"
],
fixVersions
=
jira_fix_versions
,
comment
=
comment
,
resolution
=
{
'id'
:
resolution
.
raw
[
'id'
]})
print
(
"Successfully resolved %s with fixVersions=%s!"
%
(
jira_id
,
fix_versions
))
def
resolve_jira_issues
(
title
,
merge_branches
,
comment
):
jira_ids
=
re
.
findall
(
"ROCKETMQ-[0-9]{4,5}"
,
title
)
if
len
(
jira_ids
)
==
0
:
resolve_jira_issue
(
merge_branches
,
comment
)
for
jira_id
in
jira_ids
:
resolve_jira_issue
(
merge_branches
,
comment
,
jira_id
)
def
standardize_jira_ref
(
text
):
"""
Standardize the [ROCKETMQ-XXXXX] [MODULE] prefix
Converts "[ROCKETMQ-XXX][mllib] Issue", "[MLLib] ROCKETMQ-XXX. Issue" or "ROCKETMQ XXX [MLLIB]: Issue" to
"[ROCKETMQ-XXX][MLLIB] Issue"
"""
jira_refs
=
[]
components
=
[]
# If the string is compliant, no need to process any further
if
(
re
.
search
(
r
'^\[ROCKETMQ-[0-9]{3,6}\](\[[A-Z0-9_\s,]+\] )+\S+'
,
text
)):
return
text
# Extract JIRA ref(s):
pattern
=
re
.
compile
(
r
'(ROCKETMQ[-\s]*[0-9]{3,6})+'
,
re
.
IGNORECASE
)
for
ref
in
pattern
.
findall
(
text
):
# Add brackets, replace spaces with a dash, & convert to uppercase
jira_refs
.
append
(
'['
+
re
.
sub
(
r
'\s+'
,
'-'
,
ref
.
upper
())
+
']'
)
text
=
text
.
replace
(
ref
,
''
)
# Extract rocketmq component(s):
# Look for alphanumeric chars, spaces, dashes, periods, and/or commas
pattern
=
re
.
compile
(
r
'(\[[\w\s,-\.]+\])'
,
re
.
IGNORECASE
)
for
component
in
pattern
.
findall
(
text
):
components
.
append
(
component
.
upper
())
text
=
text
.
replace
(
component
,
''
)
# Cleanup any remaining symbols:
pattern
=
re
.
compile
(
r
'^\W+(.*)'
,
re
.
IGNORECASE
)
if
(
pattern
.
search
(
text
)
is
not
None
):
text
=
pattern
.
search
(
text
).
groups
()[
0
]
# Assemble full text (JIRA ref(s), module(s), remaining text)
clean_text
=
''
.
join
(
jira_refs
).
strip
()
+
''
.
join
(
components
).
strip
()
+
" "
+
text
.
strip
()
# Replace multiple spaces with a single space, e.g. if no jira refs and/or components were
# included
clean_text
=
re
.
sub
(
r
'\s+'
,
' '
,
clean_text
.
strip
())
return
clean_text
def
get_current_ref
():
ref
=
run_cmd
(
"git rev-parse --abbrev-ref HEAD"
).
strip
()
if
ref
==
'HEAD'
:
# The current ref is a detached HEAD, so grab its SHA.
return
run_cmd
(
"git rev-parse HEAD"
).
strip
()
else
:
return
ref
def
main
():
global
original_head
os
.
chdir
(
ROCKETMQ_HOME
)
original_head
=
get_current_ref
()
latest_branch
=
DEVELOP_BRANCH
pr_num
=
raw_input
(
"Which pull request would you like to merge? (e.g. 34): "
)
pr
=
get_json
(
"%s/pulls/%s"
%
(
GITHUB_API_BASE
,
pr_num
))
pr_events
=
get_json
(
"%s/issues/%s/events"
%
(
GITHUB_API_BASE
,
pr_num
))
url
=
pr
[
"url"
]
# Decide whether to use the modified title or not
modified_title
=
standardize_jira_ref
(
pr
[
"title"
])
if
modified_title
!=
pr
[
"title"
]:
print
(
"I've re-written the title as follows to match the standard format:"
)
print
(
"Original: %s"
%
pr
[
"title"
])
print
(
"Modified: %s"
%
modified_title
)
result
=
raw_input
(
"Would you like to use the modified title? (y/n): "
)
if
result
.
lower
()
==
"y"
:
title
=
modified_title
print
(
"Using modified title:"
)
else
:
title
=
pr
[
"title"
]
print
(
"Using original title:"
)
print
(
title
)
else
:
title
=
pr
[
"title"
]
body
=
pr
[
"body"
]
target_ref
=
pr
[
"base"
][
"ref"
]
user_login
=
pr
[
"user"
][
"login"
]
base_ref
=
pr
[
"head"
][
"ref"
]
pr_repo_desc
=
"%s/%s"
%
(
user_login
,
base_ref
)
# Merged pull requests don't appear as merged in the GitHub API;
# Instead, they're closed by asfgit.
merge_commits
=
\
[
e
for
e
in
pr_events
if
e
[
"actor"
][
"login"
]
==
"asfgit"
and
e
[
"event"
]
==
"closed"
]
if
merge_commits
:
merge_hash
=
merge_commits
[
0
][
"commit_id"
]
message
=
get_json
(
"%s/commits/%s"
%
(
GITHUB_API_BASE
,
merge_hash
))[
"commit"
][
"message"
]
print
(
"Pull request %s has already been merged, assuming you want to backport"
%
pr_num
)
commit_is_downloaded
=
run_cmd
([
'git'
,
'rev-parse'
,
'--quiet'
,
'--verify'
,
"%s^{commit}"
%
merge_hash
]).
strip
()
!=
""
if
not
commit_is_downloaded
:
fail
(
"Couldn't find any merge commit for #%s, you may need to update HEAD."
%
pr_num
)
print
(
"Found commit %s:
\n
%s"
%
(
merge_hash
,
message
))
cherry_pick
(
pr_num
,
merge_hash
,
latest_branch
)
sys
.
exit
(
0
)
if
not
bool
(
pr
[
"mergeable"
]):
msg
=
"Pull request %s is not mergeable in its current form.
\n
"
%
pr_num
+
\
"Continue? (experts only!)"
continue_maybe
(
msg
)
print
(
"
\n
=== Pull Request #%s ==="
%
pr_num
)
print
(
"title
\t
%s
\n
source
\t
%s
\n
target
\t
%s
\n
url
\t
%s"
%
(
title
,
pr_repo_desc
,
target_ref
,
url
))
continue_maybe
(
"Proceed with merging pull request #%s?"
%
pr_num
)
merged_refs
=
[
target_ref
]
merge_hash
=
merge_pr
(
pr_num
,
target_ref
,
title
,
body
,
pr_repo_desc
)
pick_prompt
=
"Would you like to pick %s into another branch?"
%
merge_hash
while
raw_input
(
"
\n
%s (y/n): "
%
pick_prompt
).
lower
()
==
"y"
:
merged_refs
=
merged_refs
+
[
cherry_pick
(
pr_num
,
merge_hash
,
latest_branch
)]
if
JIRA_IMPORTED
:
if
JIRA_USERNAME
and
JIRA_PASSWORD
:
continue_maybe
(
"Would you like to update an associated JIRA?"
)
jira_comment
=
"Issue resolved by pull request %s
\n
[%s/%s]"
%
\
(
pr_num
,
GITHUB_BASE
,
pr_num
)
resolve_jira_issues
(
title
,
merged_refs
,
jira_comment
)
else
:
print
(
"JIRA_USERNAME and JIRA_PASSWORD not set"
)
print
(
"Exiting without trying to close the associated JIRA."
)
else
:
print
(
"Could not find jira-python library. Run 'sudo pip install jira' to install."
)
print
(
"Exiting without trying to close the associated JIRA."
)
if
__name__
==
"__main__"
:
import
doctest
(
failure_count
,
test_count
)
=
doctest
.
testmod
()
if
failure_count
:
exit
(
-
1
)
try
:
main
()
except
:
clean_up
()
raise
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录