duppr_analysis/experiment_code/rq3.py

468 lines
15 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#coding:utf-8
import pandas as pd
import sys
import time
import MySQLdb
import json
with open("_config.json") as fp:
config = json.load(fp)
local_db_config = config["local_db"]
conn = MySQLdb.connect(host=local_db_config["db_host"],user=local_db_config["db_user"],
passwd=local_db_config["db_passwd"],db=local_db_config["db_name"],port=3306,charset='utf8mb4')
cursor = conn.cursor()
# github id 到 ghtorrent的 id 转换
gh_gt = {}
gt_gh = {}
with open("prj_par.txt","r")as fp:
for line in fp.readlines():
ps = line.split("\t")
gt_gh[int(ps[0])] = int(ps[1])
gh_gt[int(ps[1])] = int(ps[0])
def _author_core():
cursor.execute("select repo_id,name from user_role ")
cores = set(["%d-%s"%(gh_gt[item[0]],item[1]) for item in cursor.fetchall()])
cursor.execute("select prj_id,mst_pr,dup_pr from duplicate")
dups = cursor.fetchall()
for dup in dups:
repo_id, dup_pr, mst_pr = dup
core_dup,core_mst = 0,0
cursor.execute("select author from `pull-request` where prj_id=%s and pr_num=%s",
(repo_id,dup_pr))
pr_author = cursor.fetchone()[0]
if "%d-%s"%(repo_id, pr_author) in cores:
core_dup = 1
cursor.execute("select author from `pull-request` where prj_id=%s and pr_num=%s",
(repo_id,mst_pr))
pr_author = cursor.fetchone()[0]
if "%d-%s"%(repo_id, pr_author) in cores:
core_mst = 1
cursor.execute("update compt_metrics set author_core =%s where repo_id=%s and pr_num=%s",
(core_dup,repo_id,dup_pr))
cursor.execute("update compt_metrics set author_core =%s where repo_id=%s and pr_num=%s",
(core_dup,repo_id,mst_pr))
conn.commit()
def _pr_order():
cursor.execute("select prj_id,author,pr_num from `pull-request`")
pr_history = {}
for item in cursor.fetchall():
if item[0] not in pr_history:
pr_history[item[0]] = {}
if item[1] not in pr_history[item[0]]:
pr_history[item[0]][item[1]] = []
pr_history[item[0]][item[1]].append(item[2])
for repo_id, a_p in pr_history.items():
for author, prs in a_p.items():
pr_history[repo_id][author] = sorted(prs)
cursor.execute("select prj_id,mst_pr,dup_pr from duplicate")
dups = cursor.fetchall()
for dup in dups:
repo_id, dup_pr, mst_pr = dup
cursor.execute("select author from `pull-request` where prj_id=%s and pr_num=%s",
(repo_id,dup_pr))
pr_author = cursor.fetchone()[0]
prs = pr_history[repo_id][pr_author]
pr_pos = prs.index(dup_pr)
if pr_pos == 0:
pr_order_dup = [1]
else:
pr_order_dup = [0]
cursor.execute("select author from `pull-request` where prj_id=%s and pr_num=%s",
(repo_id,mst_pr))
pr_author = cursor.fetchone()[0]
prs = pr_history[repo_id][pr_author]
pr_pos = prs.index(mst_pr)
if pr_pos == 0:
pr_order_mst = [1]
else:
pr_order_mst = [0]
pr_order_dup.extend([repo_id,dup_pr])
pr_order_mst.extend([repo_id,mst_pr])
cursor.execute("update compt_metrics set first_pr =%s where repo_id=%s and pr_num=%s",
pr_order_dup)
cursor.execute("update compt_metrics set first_pr =%s where repo_id=%s and pr_num=%s",
pr_order_mst)
conn.commit()
from unidiff import PatchSet
from io import StringIO
def _pr_work(repo_id, pr_num):
cursor.execute("select diff from pr_diff where project_id=%s and pr_id=%s",
(repo_id, pr_num))
pr_diff = cursor.fetchone()
if pr_diff is None or len(pr_diff[0].strip()) == 0:
return (0,0,repo_id, pr_num)
diff_files = PatchSet(StringIO(pr_diff[0]))
tmp_files = 0
tmp_lines = 0
for df in diff_files:
tmp_files += 1
tmp_lines += (df.added + df.removed)
return (tmp_files,tmp_lines,repo_id, pr_num)
def _work():
cursor.execute("select prj_id,mst_pr,dup_pr from duplicate")
dups = cursor.fetchall()
for dup in dups:
repo_id, dup_pr, mst_pr = dup
pr_work = _pr_work(repo_id,dup_pr)
cursor.execute("update compt_metrics set files=%s,churn =%s where repo_id=%s and pr_num=%s",
pr_work )
pr_work = _pr_work(repo_id,mst_pr)
cursor.execute("update compt_metrics set files=%s,churn =%s where repo_id=%s and pr_num=%s",
pr_work )
conn.commit()
def _strtime2int(strtime):
return time.mktime(time.strptime(strtime, '%Y-%m-%dT%H:%M:%SZ'))
def _rw_dur(repo_id, pr_num):
cursor.execute("select pr_created_at, pr_closed_at from pr_time_info where base_repo=%s and pr_num=%s",
(repo_id, pr_num))
result = cursor.fetchone()
pr_ct = _strtime2int(result[0])
if result[1] is None or len(result[1]) == 0:
return (pr_ct, sys.maxint, None)
pr_dt = _strtime2int(result[1][2:])
return (pr_ct, pr_dt, result[1][0])
def _cmts(repo_id, pr_num, ind_t):
cursor.execute("select id from `pull-request` where prj_id=%s and pr_num=%s",
(repo_id, pr_num))
pr_id = cursor.fetchone()[0]
cursor.execute("select comment_type,created_at from comments where pr_id=%s",(pr_id,))
cmts = cursor.fetchall()
cmt_count = 0
inline_cmt_count = 0
for cmt in cmts:
if cmt[1] < ind_t:
cmt_count += 1
if cmt[0] == 0:
inline_cmt_count += 1
return cmt_count, inline_cmt_count
def _time_info():
cursor.execute("select prj_id,mst_pr,dup_pr,idn_cmt from duplicate")
dups = cursor.fetchall()
for dup in dups:
repo_id, dup_pr, mst_pr, idn_cmt = dup
cursor.execute("update compt_metrics set early_arrival=1 where repo_id=%s and pr_num=%s",
(repo_id, mst_pr))
cursor.execute("update compt_metrics set early_arrival=0 where repo_id=%s and pr_num=%s",
(repo_id, dup_pr))
dup_ct, dup_dt, dup_d = _rw_dur(gt_gh[repo_id],dup_pr)
mst_ct, mst_dt, mst_d = _rw_dur(gt_gh[repo_id],mst_pr)
cursor.execute("select created_at from comment where id=%s",(idn_cmt,))
idn_t = cursor.fetchone()[0]
dup_cmts, dup_inl_cmts = _cmts(repo_id,dup_pr,idn_t)
mst_cmts, mst_inl_cmts = _cmts(repo_id,mst_pr,idn_t)
together = 1 # 是否mst 都关闭(merge 或者 close)了dup才又提交的
if mst_dt is not None and dup_ct > mst_dt:
together = 0
idn_t = _strtime2int(idn_t)
dup_prefer = 1 # 到底选择了哪一个
# 被merge的表示被倾向了如果都是close那就看谁的时间晚谁就被倾向了都是merge的不考虑
if mst_d == "M":
dup_prefer = 0 # mst 被merge了
else:
if dup_d == "M": # mst 被close了dup被merge了
dup_prefer = 1
else:
# mst:C、O, dup: C、O
if mst_d is None and dup_d is None:
dup_prefer = None
else:
if dup_dt < mst_dt: # mst被close了dup被close了但是dup被close的更早是否需要设置两个close间隔长一些因为有可能是人忘了关了
dup_prefer = 0
cursor.execute("update compt_metrics set review_duration=%s, together=%s, prefer=%s, comments=%s, inline_cmts=%s where repo_id=%s and pr_num=%s",
(idn_t - dup_ct, together, dup_prefer,dup_cmts,dup_inl_cmts, repo_id, dup_pr))
cursor.execute("update compt_metrics set review_duration=%s, together=%s, prefer=%s, comments=%s, inline_cmts=%s where repo_id=%s and pr_num=%s",
(idn_t - mst_ct, together, (dup_prefer+1)%2, mst_cmts,mst_inl_cmts, repo_id, mst_pr))
conn.commit()
def init():
cursor.execute("select prj_id,mst_pr,dup_pr from duplicate")
dups = cursor.fetchall()
for dup in dups:
repo_id, dup_pr, mst_pr = dup
cursor.execute("insert into compt_metrics(repo_id, pr_num) values(%s,%s)", (repo_id, dup_pr))
cursor.execute("insert into compt_metrics(repo_id, pr_num) values(%s,%s)", (repo_id, mst_pr))
conn.commit()
def find_new_c():
cursor.execute("select prj_id,mst_pr,dup_pr from duplicate")
dups = cursor.fetchall()
for dup in dups:
repo_id, dup_pr, mst_pr = dup
cursor.execute("select author_core, prefer from compt_metrics where repo_id=%s and pr_num=%s",
(repo_id, mst_pr))
mst_info = cursor.fetchone()
cursor.execute("select author_core, prefer from compt_metrics where repo_id=%s and pr_num=%s",
(repo_id, dup_pr))
dup_info = cursor.fetchone()
if mst_info[0]==1 and mst_info[1]==0 and dup_info[0] == 0 and dup_info[1]==1:
print repo_id, dup_pr, mst_pr
if mst_info[0]==0 and mst_info[1]==1 and dup_info[0] == 1 and dup_info[1]==0:
print repo_id, dup_pr, mst_pr
def prior_exp():
# 加载贡献经验数据
sub_exp = {}
with open("exp_data/prj_all_pr_sub.txt") as fp:
for item in fp.readlines():
ids, value = item.strip().split("\t")
if ids not in sub_exp:
sub_exp[ids] = int(value)
with open("exp_data/prj_all_issue_sub.txt") as fp:
for item in fp.readlines():
ids, value = item.strip().split("\t")
if ids not in sub_exp:
sub_exp[ids] = int(value)
else:
sub_exp[ids] += int(value)
for key, value in sub_exp.items():
repo_id, pr_num = key.split("-")
cursor.execute("update compt_metrics set sub_exp=%s where repo_id=%s and pr_num=%s",
(value,repo_id,pr_num))
conn.commit()
# 加载评论经验数据
cmt_sub = {}
with open("exp_data/prj_all_pr_cmt.txt") as fp:
for item in fp.readlines():
ids, value = item.strip().split("\t")
if ids not in cmt_sub:
cmt_sub[ids] = int(value)
with open("exp_data/prj_all_issue_cmt.txt") as fp:
for item in fp.readlines():
ids, value = item.strip().split("\t")
if ids not in cmt_sub:
cmt_sub[ids] = int(value)
else:
cmt_sub[ids] += int(value)
for key, value in cmt_sub.items():
repo_id, pr_num = key.split("-")
cursor.execute("update compt_metrics set cmt_exp=%s where repo_id=%s and pr_num=%s",
(value,repo_id,pr_num))
conn.commit()
def metric_dist():
fields = "author_core, sub_exp, cmt_exp, first_pr, early_arrival, churn, files, review_duration, comments, inline_cmts"
cursor.execute("select " + fields + " from compt_metrics where together=1")
metrics = cursor.fetchall()
mlst = []
fields = fields.split(", ")
for item in fields:
mlst.append([])
for metric in metrics:
for i in range(0, len(metric)):
mlst[i].append(metric[i])
for i in range(0, len(fields)):
print fields[i], ": "
ipd = pd.Series(mlst[i])
print ipd.describe()
print ">>>>>>>" * 10
def veriry_metrics():
import random
cursor.execute("select * from compt_metrics")
metrics = [item for item in cursor.fetchall()]
random.shuffle(metrics)
for metric in metrics[:10]:
repo_id,pr_num = metric[1], metric[2]
cursor.execute("select user_name, repo_name from project where id=%s",(repo_id,))
u_n, r_n = cursor.fetchone()
print repo_id,pr_num
print "https://github.com/%s/%s/pull/%d"%(u_n, r_n, pr_num)
# 2019-02-27以后的代码
# 计算每一个pr所有评论的情感极性
# 只看issue级别的如果有@人的话,说明是在对话,可以先着重看@作者的;应该说越往后的评论越重要
def get_deci_time(repo_id, dup_pr, mst_pr):
'''获取一对重复pr的决策时间 '''
# 哪一个被先关闭了或者被先合并了
# 现获取各自的时间点
repo_id = gt_gh[repo_id]
dup_ct, dup_dt, dup_D = _rw_dur(repo_id, dup_pr)
mst_ct, mst_dt, mst_D = _rw_dur(repo_id, mst_pr)
# 判断是否有overlap #其实只看dup_ct与mst_dt的关系就可以了因为dup是晚于mst提交的
if dup_ct > mst_dt or mst_ct > dup_dt:
return None
# 判断决策时间
if dup_dt < mst_dt:
return dup_dt
else:
return mst_dt
import re
import subprocess
import shlex
def _txt_clean(text):
'''清洗文本'''
# 把评论中的一些干扰项去除掉
# 代码去掉
text = re.sub('(```[\s\S]*?```|``[\s\S]*?``|`[\s\S]*?`)',"@@cmmcode@@",text)
# 他人的引用去掉
text = re.sub('(>.*\n|on.*\nwrote:)', "@@cmmref@@", text.lower())#去掉引用块
# 超链接去掉
text = re.sub("((http|ftp|https)://)(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)?","@@cmmhttp@@",text)#去掉http
# 把换行符去掉
text = re.sub(r"\n"," ", text)
# 把多个空格替换成一个空格
text = re.sub(r"\s+"," ", text)
return text
def _txt_sent(txt):
'''一段文本的情感'''
# print "\tbc: ", txt.strip()
txt = _txt_clean(txt)
# print "\tac: ", txt
ss_data_path = "/Users/lizhixing/Documents/project"
cmd_str = "java -jar SentiStrength.jar stdin sentidata SentiStrength_Data/ explain additionalFile ReviewCommentAddition.txt"
p = subprocess.Popen(shlex.split(cmd_str), cwd=ss_data_path,
stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout_text, stderr_text = p.communicate(txt)
sentis = [item.strip() for item in stdout_text.split("\t")]
# extract the positive value, negative value, and explanation.
# print "\t", sentis
return sentis[:2]
def _cmt_senti(repo_id, pr_num, decide_time):
'''在decide_time前提交的几个comment的情感分析'''
cursor.execute("select id,author from `pull-request` where prj_id=%s and pr_num=%s",
(repo_id, pr_num))
pr_id, pr_author = cursor.fetchone()
print pr_id, repo_id, pr_num, pr_author
# 取出pr的所有comment
cursor.execute("select author_name, comment_body, comment_type, created_at,id from comments where pr_id=%s",(pr_id,))
cmts = cursor.fetchall()
print"\ttotal:",len(cmts)
# 挑选出非作者本人的评论非决策comment到底还留不留呢有些是夸奖两句直接接受了
cmts = [item for item in cmts if item[0] != pr_author]
print"\tno pr_author:",len(cmts)
# print"\n"
# 按时间排序从决策时间往前看n个
decide_time = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.localtime(decide_time))
cmts = [item for item in sorted(cmts, key=lambda x:x[3], reverse=True) if item[3]<=decide_time]
if len(cmts) == 0:
return
senti_num = [0,0] #记录各种极性情感的评论的个数
for cmt in cmts:
# print "\t", cmt[0], cmt[3], cmt[2]
cmt_senti = _txt_sent(cmt[1])
if int(cmt_senti[0]) > 1:
senti_num[0] += 1
if int(cmt_senti[1]) < -1:
senti_num[1] += 1
# print "\n"
print "\t SENTI_P: ", senti_num, [1.0*item/len(cmts) for item in senti_num]
def load_cust_cfg():
# 把项目中的定制化配置文件更新到 sentistrength相应目录下
ss_dir = ss_data_path = "/Users/lizhixing/Documents/project/SentiStrength_Data"
with open("%s/ReviewCommentAddition.txt"%(ss_dir,),"w+") as t_fp:
t_fp.truncate()
with open("custom-config/ReviewCommentAddition.txt","r+") as s_fp:
for line in s_fp:
t_fp.write(line)
with open("%s/EmoticonLookupTable.txt"%(ss_dir,),"w+") as t_fp:
t_fp.truncate()
with open("custom-config/EmoticonLookupTable.txt","r+") as s_fp:
for line in s_fp:
t_fp.write(line)
# 目前配置的有
# lgtm: 3.
def cmt_sentiment():
# 可以这样:每一个评论有这样的极性得分[p,n]然后看他所有的评论中p>1的比例有多少n<-1的比例有多少
# 也就说有两个指标,包含积极情感的审阅意见占比; 包含消极情感的审阅意见占比
load_cust_cfg()
cursor.execute("select prj_id,mst_pr,dup_pr,idn_cmt from duplicate")
import random
dups = [list(item) for item in cursor.fetchall()] #cvt 2 list to shuffle
random.shuffle(dups)
for dup in dups[:5]:
# 取出pr的相关信息
repo_id, dup_pr, mst_pr, idn_cmt = dup
print dup
# 判断两个pr的决策时间
decide_time = get_deci_time(repo_id,mst_pr,dup_pr)
if decide_time is None:
print "no overlapped"
print "*"*20
continue
# 挨个判断
_cmt_senti(repo_id, dup_pr, decide_time)
_cmt_senti(repo_id, mst_pr, decide_time)
print "*"*20
if __name__ == '__main__':
# init()
# _author_core()
# _pr_order()
# _work()
# _time_info()
# find_new_c()
# prior_exp()
# metric_dist()
# veriry_metrics()
# 测试情感分析效果
# load_cust_cfg()
# cmt_str = "lgtm, we can merge this pr. +1"
# _txt_sent(cmt_str)
cmt_sentiment()