forked from Gitlink/forgeplus-react
525 lines
18 KiB
JavaScript
525 lines
18 KiB
JavaScript
import React, { Component } from 'react';
|
||
import { Input, Select, Spin, Alert } from 'antd';
|
||
import axios from 'axios';
|
||
import MergeForm from './merge_form';
|
||
import MergeFooter from './merge_footer';
|
||
import { returnbar , turnbar } from 'educoder';
|
||
import { Base64 } from 'js-base64';
|
||
|
||
import '../Order/order.scss';
|
||
import './merge.css';
|
||
|
||
/**
|
||
* 根据url获取目标仓库、目标分支、源仓库、源分支
|
||
* 路由规则:owner/projectId/compare/merge...pullowner:pullBranch
|
||
* 可能存在的情况
|
||
* 1、代码库首页跳转,仓库相同,目标分支为默认分支,owner/projectId/compare/pullBranch
|
||
* 2、代码库分支列表,仓库相同,目标分支为默认分支,owner/projectId/compare/pullBranch
|
||
* 3、合并请求列表页(新建、无数据时的提示),仓库相同,源、目标都为默认分支,owner/projectId/compare
|
||
* 4、新建页面,切换分支、切换目标仓库、刷新页面等,存在所有可能情况
|
||
*/
|
||
const limit = 15;
|
||
|
||
function getBranchParams(pathname) {
|
||
const result = {
|
||
// 目标仓库所有者
|
||
mergeOwner: undefined,
|
||
// 目标分支
|
||
mergeBranch: 'master',
|
||
// 源仓库所有者
|
||
pullOwner: undefined,
|
||
// 源分支
|
||
pullBranch: 'master',
|
||
// 仓库名称
|
||
projectId: undefined,
|
||
};
|
||
// 去掉第一个字符/
|
||
const _pathname = pathname.slice(1);
|
||
const [ownerProject, branchUrl] = _pathname.split('/compare');
|
||
const [mergeOwner, projectId] = ownerProject.split('/');
|
||
// 同仓库时
|
||
result.mergeOwner = mergeOwner;
|
||
result.pullOwner = mergeOwner;
|
||
result.projectId = projectId;
|
||
if (branchUrl) {
|
||
// 如果存在具体的分支
|
||
const _branchUrl = branchUrl.slice(1);
|
||
if (_branchUrl.indexOf('...') > -1) {
|
||
// 存在源分支与目标分支
|
||
const [mergeBranch, pullObj] = _branchUrl.split('...');
|
||
result.mergeBranch = mergeBranch;
|
||
if (pullObj.indexOf(':') > -1) {
|
||
// 存在源仓库
|
||
const [pullOwner, pullBranch] = pullObj.split(':');
|
||
if(pullOwner.indexOf("/")>-1){
|
||
const [ ownerO , onwerP ] = pullOwner.split('/');
|
||
result.pullOwner = ownerO;
|
||
result.pullIdentity = onwerP;
|
||
}else{
|
||
result.pullOwner = pullOwner;
|
||
}
|
||
|
||
result.pullBranch = pullBranch;
|
||
} else {
|
||
result.pullBranch = pullObj;
|
||
}
|
||
} else {
|
||
result.pullBranch = _branchUrl;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
const Option = Select.Option;
|
||
|
||
class CreateMerge extends Component {
|
||
constructor(props) {
|
||
super(props);
|
||
const { pullBranch, mergeBranch } = getBranchParams(
|
||
this.props.location.pathname
|
||
);
|
||
this.state = {
|
||
data: undefined,
|
||
pullBranches: undefined,
|
||
mergeBranches: undefined,
|
||
mergeProjects: undefined,
|
||
merge: returnbar(mergeBranch) || 'master',
|
||
pull: returnbar(pullBranch) || 'master',
|
||
id: undefined,
|
||
// isFork: false,
|
||
projects_names: undefined,
|
||
isSpin: true,
|
||
showMessage: false,
|
||
merge_head: false, // 是否向fork后的源项目发起合并请求
|
||
defaultMessage: '必须选择不同的分支',
|
||
project_id: undefined, // 当前项目的id,也即开始发送合并请求的源项目id
|
||
merge_project_user: undefined,
|
||
comparesData: undefined, //提交和文件的内容,保存compare接口返回的数据
|
||
// 比较分支时的加载效果
|
||
isCompareSpin: true,
|
||
// 是否是初次加载,用这个字段来控制提示组件和文件组件的显示、隐藏比直接用isCompareSpin交互友好些
|
||
isFirstLoading: true,
|
||
};
|
||
}
|
||
|
||
componentDidMount = () => {
|
||
this.updateDocumentTitle();
|
||
// 初始化时根据url获取目标仓库、分支,源仓库、分支;
|
||
// 再获取对应的仓库列表、分支列表
|
||
// 再调用比较接口
|
||
const branchParams = getBranchParams(this.props.location.pathname);
|
||
this.getMergeInfo(branchParams,true);
|
||
this.checkBranch(branchParams);
|
||
};
|
||
|
||
componentDidUpdate = (preProps) => {
|
||
this.updateDocumentTitle();
|
||
// url变化触发时(切换源分支、切换目标仓库、切换目标分支;回退)
|
||
const oldPathname = preProps.location.pathname;
|
||
const newPathname = this.props.location.pathname;
|
||
if (oldPathname !== newPathname) {
|
||
const branchParams = getBranchParams(newPathname);
|
||
this.getMergeInfo(branchParams);
|
||
this.checkBranch(branchParams);
|
||
}
|
||
};
|
||
|
||
// 更新网页标题
|
||
updateDocumentTitle(){
|
||
const {projectDetail} = this.props;
|
||
if(projectDetail && document.title.indexOf('新建合并请求-') === -1){
|
||
const { author, name} = projectDetail;
|
||
document.title = `新建合并请求-${author.name}/${name}`;
|
||
}
|
||
}
|
||
|
||
//获取新建合并请求数据
|
||
getMergeInfo = (branchParams,init) => {
|
||
this.setState({ isSpin: true });
|
||
const { pullOwner, pullBranch, mergeOwner, mergeBranch, projectId , pullIdentity } = branchParams;
|
||
const url = `/${pullOwner}/${pullIdentity || projectId}/pulls/new.json`;
|
||
axios.get(url).then((result) => {
|
||
if (result) {
|
||
if(init){
|
||
//页面初始化时调用selectProjectName,给is_original、fork_project_id、merge_user_login赋值
|
||
const initUser = result.data.projects_names && result.data.projects_names.filter((item)=>item.project_user_login === branchParams.mergeOwner);
|
||
initUser && initUser[0] && this.selectProjectName(initUser[0].id,false,{projects_names: result.data.projects_names,id: result.data.id});
|
||
}
|
||
this.setState({
|
||
// isFork: result.data.is_fork,
|
||
projects_names: result.data.projects_names,
|
||
mergeProjects: result.data.merge_projects,
|
||
// pullBranches: result.data.branches,
|
||
// mergeBranches: result.data.branches,
|
||
project_id: result.data.project_id,
|
||
id: result.data.id,
|
||
merge: returnbar(mergeBranch),
|
||
pull: returnbar(pullBranch),
|
||
});
|
||
// this.setState({
|
||
// isSpin: false,isCompareSpin: false
|
||
// });
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
this.setState({ isSpin: false });
|
||
console.log(error);
|
||
});
|
||
};
|
||
|
||
checkBranch=async(branchParams)=>{
|
||
const { mergeBranch , mergeOwner, projectId , pullBranch ,pullOwner,pullIdentity} = branchParams;
|
||
let getbranch =await this.getBranchList(mergeOwner,projectId,pullBranch,mergeBranch,"merge");
|
||
let checkpull =await this.getBranchList(pullOwner,pullIdentity || projectId,pullBranch,mergeBranch,"pull");
|
||
if(getbranch && checkpull){
|
||
this.compareProject(mergeOwner === pullOwner, branchParams,1);
|
||
}else{
|
||
this.setState({
|
||
isSpin: false,isCompareSpin: false
|
||
});
|
||
}
|
||
}
|
||
|
||
// compare接口,获取分支对比信息
|
||
compareProject = (sameProject, branchParams ,page) => {
|
||
const { pullOwner, pullBranch, mergeOwner, mergeBranch, projectId , pullIdentity } = branchParams;
|
||
let url = `/${mergeOwner}/${projectId}/compare`;
|
||
if (sameProject) {
|
||
url += `/${Base64.encode(returnbar(pullBranch))}...${Base64.encode(returnbar(mergeBranch))}.json`;
|
||
} else {
|
||
url += `/${Base64.encode(returnbar(mergeBranch))}...${pullOwner}/${pullIdentity || projectId}:${Base64.encode(returnbar(pullBranch))}.json`;
|
||
}
|
||
axios
|
||
.get(url,{
|
||
params:{limit,page}
|
||
})
|
||
.then((result) => {
|
||
if (result) {
|
||
if (result.data.status === 0) {
|
||
this.setState({
|
||
showMessage: false,
|
||
});
|
||
} else {
|
||
this.setState({
|
||
showMessage: true,
|
||
defaultMessage: result.data.message,
|
||
});
|
||
}
|
||
this.setState({
|
||
comparesData: result.data,
|
||
isSpin: false,isCompareSpin: false,isFirstLoading:false
|
||
});
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
this.setState({ isSpin: false, isCompareSpin: false });
|
||
});
|
||
};
|
||
|
||
// 根据所有者、仓库名,获取分支列表,目前仅涉及目标仓库分支查询
|
||
getBranchList =async(owner,identifier,branch,mergebranch,type) => {
|
||
const url = `/${owner}/${identifier}/branches.json`;
|
||
let check = await axios.get(url).then((result) => {
|
||
if(result.data){
|
||
let pfilter = (result.data || []).filter(i => i.name === decodeURIComponent(branch)).length === 0;
|
||
let mfilter = (result.data || []).filter(i => i.name === decodeURIComponent(mergebranch)).length === 0;
|
||
if(type === "pull"){
|
||
this.setState({
|
||
pullBranches:result.data,
|
||
})
|
||
}else{
|
||
this.setState({
|
||
mergeBranches:result.data
|
||
})
|
||
}
|
||
if(type === "pull" && pfilter){
|
||
this.setState({
|
||
showMessage: branch,
|
||
defaultMessage: `源分支不存在`,
|
||
isCompareSpin: false
|
||
});
|
||
return false;
|
||
}
|
||
if(type === "merge" && mfilter){
|
||
const {projectDetail,location,history}=this.props;
|
||
const { pullOwner, pullBranch, mergeOwner, mergeBranch, projectId , pullIdentity } =
|
||
getBranchParams(location.pathname);
|
||
// 如果进来的第一次,查到目标分支不存在,自动切换到目标仓库默认分支
|
||
if (pullOwner !== mergeOwner && pullBranch=== mergeBranch) {
|
||
let _url = `/${mergeOwner}/${projectId}/compare/${(projectDetail?projectDetail.default_branch:'master')}...${pullOwner}/${pullIdentity || projectId}:${(pullBranch)}`;
|
||
history.push(_url);
|
||
return false;
|
||
}else{
|
||
this.setState({
|
||
showMessage: mergebranch,
|
||
defaultMessage: `目标分支不存在`,
|
||
isCompareSpin: false
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
this.setState({ isSpin: false });
|
||
check = false;
|
||
});
|
||
return check;
|
||
};
|
||
|
||
// 切换分支事件
|
||
selectBrach = (type, value) => {
|
||
const { pullOwner, pullBranch, mergeOwner, mergeBranch, projectId , pullIdentity } =
|
||
getBranchParams(this.props.location.pathname);
|
||
let _url = `/${mergeOwner}/${projectId}/compare/`;
|
||
// type为pull时,pullBranch取value,否则取原有值
|
||
// type为pull时,mergeBranch取原有值,否则取value
|
||
let _pullBranch = type === 'pull' ? value : returnbar(pullBranch);
|
||
let _mergeBranch = type === 'pull' ? returnbar(mergeBranch) : value;
|
||
if (pullOwner === mergeOwner) {
|
||
// 如果仓库相同, compare/目标分支...源分支
|
||
_url += `${turnbar(_mergeBranch)}...${turnbar(_pullBranch)}`;
|
||
} else {
|
||
// 如果仓库不同, compare/目标分支...源分支
|
||
_url += `${turnbar(_mergeBranch)}...${pullOwner}/${pullIdentity || projectId}:${turnbar(_pullBranch)}`;
|
||
}
|
||
this.props.history.push(_url);
|
||
};
|
||
|
||
// 切换仓库响应事件,目前仅目标分支可切换仓库
|
||
selectProjectName = (value, isChangeProject, initPro) => {
|
||
const { projects_names, id } = isChangeProject ? this.state : initPro;
|
||
const { pullOwner, pullBranch , pullIdentity , projectId } = getBranchParams(
|
||
this.props.location.pathname
|
||
);
|
||
let arr =
|
||
projects_names && projects_names.filter((item) => item.id === value);
|
||
let identifier = arr && arr[0].project_id;
|
||
let login = arr && arr[0].project_user_login;
|
||
// 目标仓库与源仓库不是一个仓库
|
||
let is_fork = parseInt(value, 10) !== parseInt(id, 10);
|
||
this.setState({
|
||
// 切换目标仓库时url会发生变化,即有spin效果
|
||
// isSpin: true,
|
||
// merge_head: is_fork,
|
||
data: {
|
||
is_original: is_fork,
|
||
fork_project_id: is_fork ? id : '',
|
||
merge_user_login: is_fork
|
||
? projects_names[0].project_user_login
|
||
: undefined,
|
||
merge_project_identifier:pullIdentity||projectId
|
||
},
|
||
});
|
||
// 加上是否需要切换url判断
|
||
if(isChangeProject){
|
||
const { defaultBranch } = this.props;
|
||
let branch = turnbar(defaultBranch) || "master";
|
||
if (login === pullOwner) {
|
||
// 如果切换后, 仓库与源仓库一致了
|
||
this.props.history.push(
|
||
`/${login}/${identifier}/compare/${branch}...${turnbar(pullBranch)}`
|
||
);
|
||
} else {
|
||
this.props.history.push(
|
||
`/${login}/${identifier}/compare/${branch}...${pullOwner}/${pullIdentity || projectId}:${turnbar(pullBranch)}`
|
||
);
|
||
}
|
||
}
|
||
// this.newMergelist(login, identifier);
|
||
};
|
||
|
||
// 渲染分支列表
|
||
renderBrances = (list) => {
|
||
if (list && list.length > 0) {
|
||
return list.map((item, key) => {
|
||
return (
|
||
<Option key={key + 1} value={item.name}>
|
||
{item.name}
|
||
</Option>
|
||
);
|
||
});
|
||
}
|
||
};
|
||
|
||
// 渲染项目列表
|
||
renderProjectNames = (list) => {
|
||
if (list && list.length > 0) {
|
||
return list.map((item, key) => {
|
||
return (
|
||
<Option key={key + 1} value={item.id}>
|
||
{item.project_name}
|
||
</Option>
|
||
);
|
||
});
|
||
}
|
||
};
|
||
|
||
// 渲染html内容
|
||
withHtml = (html) => {
|
||
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
|
||
};
|
||
|
||
// 源分支-分支搜索
|
||
changePullValue=async(value,cate)=>{
|
||
const branchParams = getBranchParams(this.props.location.pathname);
|
||
const { pullOwner, mergeOwner , projectId } = branchParams;
|
||
const url = `/${cate === "pull" ? pullOwner :mergeOwner}/${projectId}/pulls/get_branches.json`;
|
||
axios.get(url,{
|
||
params:{
|
||
name:value || undefined,
|
||
}
|
||
}).then(result=>{
|
||
if(result){
|
||
if(cate === "pull"){
|
||
this.setState({
|
||
pullBranches:result.data,
|
||
pullBranch:result.data
|
||
})
|
||
}else{
|
||
this.setState({
|
||
mergeBranches:result.data,
|
||
mergeBranch:result.data
|
||
})
|
||
}
|
||
}
|
||
}).catch(error=>{})
|
||
}
|
||
|
||
changeCommitFunc=(page)=>{
|
||
this.setState({
|
||
isSpin:true
|
||
})
|
||
const branchParams = getBranchParams(this.props.location.pathname);
|
||
const { mergeOwner , pullOwner} = branchParams;
|
||
this.compareProject(mergeOwner === pullOwner, branchParams,page);
|
||
}
|
||
|
||
render() {
|
||
const {
|
||
data,
|
||
pullBranches,
|
||
mergeBranches,
|
||
mergeProjects,
|
||
pull,
|
||
merge,
|
||
isSpin,
|
||
isCompareSpin,
|
||
isFirstLoading,
|
||
showMessage,
|
||
defaultMessage,
|
||
projects_names,
|
||
id,
|
||
comparesData,
|
||
} = this.state;
|
||
|
||
let { project } = this.props;
|
||
// 源仓库所有者login
|
||
const pullOwnerLogin = projects_names && projects_names.filter(item=>{return item.id === id})[0].project_user_login;
|
||
return (
|
||
<div>
|
||
<Spin spinning={isSpin || isCompareSpin}>
|
||
<div className="main">
|
||
<div className="merge-header width100 inline-block">
|
||
<div className="width40 pull-left">
|
||
<div className="color-grey-3 mb10 fwb">源分支 : </div>
|
||
<Input.Group compact className="display-flex">
|
||
<Select
|
||
value={id}
|
||
className="hide-1 task-hide flex1"
|
||
disabled
|
||
>
|
||
{this.renderProjectNames(projects_names)}
|
||
</Select>
|
||
<Select
|
||
value={pull}
|
||
onSelect={(e) => this.selectBrach('pull', e)}
|
||
showSearch
|
||
className="merge-flex1 flex1 matchwidth"
|
||
dropdownMatchSelectWidth={false}
|
||
dropdownClassName="overlihide"
|
||
// onSearch={(e)=>this.changePullValue(e,"pull")}
|
||
// filterOption={(input,option)=>option}
|
||
>
|
||
{this.renderBrances(pullBranches)}
|
||
</Select>
|
||
</Input.Group>
|
||
</div>
|
||
<div className="width10 pull-left text-center mt25">
|
||
<i
|
||
className={'iconfont icon-youjiang color-grey-c font-32'}
|
||
></i>
|
||
</div>
|
||
<div className="width40 pull-left">
|
||
<div>
|
||
<div className="color-grey-3 mb10 fwb">目标分支 : </div>
|
||
<Input.Group compact className="display-flex">
|
||
<Select
|
||
value={project && project.id}
|
||
className="hide-1 task-hide flex1"
|
||
onSelect={(e) => this.selectProjectName(e, true)}
|
||
>
|
||
{this.renderProjectNames(mergeProjects)}
|
||
</Select>
|
||
<Select
|
||
value={merge}
|
||
onSelect={(e) => this.selectBrach('merge', e)}
|
||
showSearch
|
||
className="merge-flex1 flex1 matchwidth"
|
||
dropdownMatchSelectWidth={false}
|
||
dropdownClassName="overlihide"
|
||
// onSearch={(e)=>this.changePullValue(e,"merge")}
|
||
// filterOption={(input,option)=>option}
|
||
>
|
||
{this.renderBrances(mergeBranches)}
|
||
</Select>
|
||
</Input.Group>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{/* 非加载状态且有提示 */}
|
||
{!isCompareSpin && showMessage && (
|
||
<div className="mb20">
|
||
<Alert
|
||
description={this.withHtml(defaultMessage)}
|
||
type="error"
|
||
/>
|
||
</div>
|
||
)}
|
||
{/* 非加载状态且可以提交 */}
|
||
{!isCompareSpin && !showMessage && (
|
||
<MergeForm
|
||
{...this.props}
|
||
merge_type="new"
|
||
data={data}
|
||
merge={merge}
|
||
pull={pull}
|
||
files_count={
|
||
comparesData &&
|
||
comparesData.diff &&
|
||
comparesData.diff.files_count
|
||
}
|
||
commits_count={comparesData && comparesData.commits_count}
|
||
></MergeForm>
|
||
)}
|
||
</div>
|
||
{!isFirstLoading && (
|
||
<MergeFooter
|
||
{...this.props}
|
||
merge={merge}
|
||
pull={pull}
|
||
limit = {limit}
|
||
changeCommitFunc={this.changeCommitFunc}
|
||
comparesData={comparesData}
|
||
pullOwnerLogin={pullOwnerLogin}
|
||
></MergeFooter>
|
||
)}
|
||
</Spin>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default CreateMerge;
|