This is an automated email from the ASF dual-hosted git repository.

warren pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 905ba803c Extract repo from Jira issue remotelink (#4526)
905ba803c is described below

commit 905ba803cf99ec436cbd86151598d9ac84b89e1c
Author: mindlesscloud <[email protected]>
AuthorDate: Tue Feb 28 13:25:34 2023 +0800

    Extract repo from Jira issue remotelink (#4526)
    
    * feat: extract repo from renotelink
    
    * refactor: change the type of JiraTransformationRule.RemotelinkRepoPattern 
to json.RawMessage
    
    * fix: remove some debug code
---
 .../domainlayer/crossdomain/issue_repo_commits.go  |   3 +
 .../20230222_add_host_namespace_reponame.go        |  55 +++++++++
 backend/core/models/migrationscripts/register.go   |   1 +
 backend/plugins/jira/api/transformation_rule.go    |   4 +
 backend/plugins/jira/e2e/issue_repo_commit_test.go |  63 ++++++++++
 ...ra_board_issues_for_ConvertIssueRepoCommits.csv |  29 +++++
 ...a_issue_commits_for_ConvertIssueRepoCommits.csv |  80 +++++++++++++
 .../e2e/snapshot_tables/issue_repo_commits.csv     |  80 +++++++++++++
 .../migrationscripts/20230223_add_repo_pattern.go  |  48 ++++++++
 .../jira/models/migrationscripts/register.go       |   1 +
 .../plugins/jira/models/transformation_rules.go    |  28 ++++-
 .../jira/tasks/issue_repo_commit_convertor.go      |  44 ++++++-
 .../jira/tasks/issue_repo_commit_convertor_test.go | 131 +++++++++++++++++++++
 backend/plugins/jira/tasks/task_data.go            |  33 +++---
 14 files changed, 579 insertions(+), 21 deletions(-)

diff --git a/backend/core/models/domainlayer/crossdomain/issue_repo_commits.go 
b/backend/core/models/domainlayer/crossdomain/issue_repo_commits.go
index 8f864ba47..911c984c3 100644
--- a/backend/core/models/domainlayer/crossdomain/issue_repo_commits.go
+++ b/backend/core/models/domainlayer/crossdomain/issue_repo_commits.go
@@ -26,6 +26,9 @@ type IssueRepoCommit struct {
        IssueId   string `gorm:"primaryKey;type:varchar(255)"`
        RepoUrl   string `gorm:"primaryKey;type:varchar(255)"`
        CommitSha string `gorm:"primaryKey;type:varchar(255)"`
+       Host      string `gorm:"type:varchar(255)"`
+       Namespace string `gorm:"type:varchar(255)"`
+       RepoName  string `gorm:"type:varchar(255)"`
 }
 
 func (IssueRepoCommit) TableName() string {
diff --git 
a/backend/core/models/migrationscripts/20230222_add_host_namespace_reponame.go 
b/backend/core/models/migrationscripts/20230222_add_host_namespace_reponame.go
new file mode 100644
index 000000000..b1f9c122b
--- /dev/null
+++ 
b/backend/core/models/migrationscripts/20230222_add_host_namespace_reponame.go
@@ -0,0 +1,55 @@
+/*
+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.
+*/
+
+package migrationscripts
+
+import (
+       "github.com/apache/incubator-devlake/core/context"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/helpers/migrationhelper"
+)
+
+var _ plugin.MigrationScript = (*addHostNamespaceRepoName)(nil)
+
+type IssueRepoCommit20220222 struct {
+       Host      string `gorm:"type:varchar(255)"`
+       Namespace string `gorm:"type:varchar(255)"`
+       RepoName  string `gorm:"type:varchar(255)"`
+}
+
+func (IssueRepoCommit20220222) TableName() string {
+       return "issue_repo_commits"
+}
+
+type addHostNamespaceRepoName struct{}
+
+func (script *addHostNamespaceRepoName) Up(basicRes context.BasicRes) 
errors.Error {
+
+       return migrationhelper.AutoMigrateTables(
+               basicRes,
+               &IssueRepoCommit20220222{},
+       )
+}
+
+func (*addHostNamespaceRepoName) Version() uint64 {
+       return 20230222153154
+}
+
+func (*addHostNamespaceRepoName) Name() string {
+       return "add host, namespace, repo_name to issue_repo_commits"
+}
diff --git a/backend/core/models/migrationscripts/register.go 
b/backend/core/models/migrationscripts/register.go
index c779d44a7..a09acdbeb 100644
--- a/backend/core/models/migrationscripts/register.go
+++ b/backend/core/models/migrationscripts/register.go
@@ -74,5 +74,6 @@ func All() []plugin.MigrationScript {
                new(modifyIssueStorypointToFloat64),
                new(addCommitShaIndex),
                new(removeCreatedDateAfterFromCollectorMeta20230223),
+               new(addHostNamespaceRepoName),
        }
 }
diff --git a/backend/plugins/jira/api/transformation_rule.go 
b/backend/plugins/jira/api/transformation_rule.go
index 72717b15b..5121b3474 100644
--- a/backend/plugins/jira/api/transformation_rule.go
+++ b/backend/plugins/jira/api/transformation_rule.go
@@ -79,6 +79,10 @@ func UpdateTransformationRule(input 
*plugin.ApiResourceInput) (*plugin.ApiResour
                return nil, errors.Default.Wrap(err, "error decoding map into 
transformationRule")
        }
        old.ID = transformationRuleId
+       err = old.VerifyRegexp()
+       if err != nil {
+               return nil, errors.Default.Wrap(err, "error verify the regexps 
of transformationRule")
+       }
        err = basicRes.GetDal().Update(&old, dal.Where("id = ?", 
transformationRuleId))
        if err != nil {
                if basicRes.GetDal().IsDuplicationError(err) {
diff --git a/backend/plugins/jira/e2e/issue_repo_commit_test.go 
b/backend/plugins/jira/e2e/issue_repo_commit_test.go
new file mode 100644
index 000000000..3535aeaec
--- /dev/null
+++ b/backend/plugins/jira/e2e/issue_repo_commit_test.go
@@ -0,0 +1,63 @@
+/*
+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.
+*/
+
+package e2e
+
+import (
+       
"github.com/apache/incubator-devlake/core/models/domainlayer/crossdomain"
+       "github.com/apache/incubator-devlake/helpers/e2ehelper"
+       "github.com/apache/incubator-devlake/plugins/jira/impl"
+       "github.com/apache/incubator-devlake/plugins/jira/models"
+       "github.com/apache/incubator-devlake/plugins/jira/tasks"
+       "testing"
+)
+
+func TestConvertIssueRepoCommitsDataFlow(t *testing.T) {
+       var plugin impl.Jira
+       dataflowTester := e2ehelper.NewDataFlowTester(t, "jira", plugin)
+
+       taskData := &tasks.JiraTaskData{
+               Options: &tasks.JiraOptions{
+                       ConnectionId: 2,
+                       BoardId:      8,
+                       TransformationRules: &tasks.JiraTransformationRule{
+                               RemotelinkCommitShaPattern: `.*/commit/(.*)`,
+                               RemotelinkRepoPattern: []string{
+                                       
`https://bitbucket.org/(?P<namespace>[^/]+)/(?P<repo_name>[^/]+)/commits/(?P<commit_sha>\w{40})`,
+                                       
`https://gitlab.com/(?P<namespace>\S+)/(?P<repo_name>\S+)/-/commit/(?P<commit_sha>\w{40})`,
+                                       
`https://github.com/(?P<namespace>[^/]+)/(?P<repo_name>[^/]+)/commit/(?P<commit_sha>\w{40})`,
+                               },
+                       },
+               },
+       }
+       dataflowTester.FlushTabler(&crossdomain.IssueRepoCommit{})
+       
dataflowTester.ImportCsvIntoTabler("./snapshot_tables/_tool_jira_issue_commits_for_ConvertIssueRepoCommits.csv",
 &models.JiraIssueCommit{})
+       
dataflowTester.ImportCsvIntoTabler("./snapshot_tables/_tool_jira_board_issues_for_ConvertIssueRepoCommits.csv",
 &models.JiraBoardIssue{})
+       dataflowTester.Subtask(tasks.ConvertIssueRepoCommitsMeta, taskData)
+       dataflowTester.VerifyTable(
+               crossdomain.IssueRepoCommit{},
+               "./snapshot_tables/issue_repo_commits.csv",
+               e2ehelper.ColumnWithRawData(
+                       "issue_id",
+                       "repo_url",
+                       "commit_sha",
+                       "host",
+                       "namespace",
+                       "repo_name",
+               ),
+       )
+}
diff --git 
a/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_board_issues_for_ConvertIssueRepoCommits.csv
 
b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_board_issues_for_ConvertIssueRepoCommits.csv
new file mode 100644
index 000000000..5ea7371b0
--- /dev/null
+++ 
b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_board_issues_for_ConvertIssueRepoCommits.csv
@@ -0,0 +1,29 @@
+connection_id,board_id,issue_id,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
+2,8,10063,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12441,
+2,8,10064,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12442,
+2,8,10065,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12444,
+2,8,10066,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12445,
+2,8,10139,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12446,
+2,8,10145,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12447,
+2,8,10159,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12450,
+2,8,10202,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12451,
+2,8,10203,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12452,
+2,8,10204,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12453,
+2,8,10205,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12454,
+2,8,10206,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12455,
+2,8,10207,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12456,
+2,8,10213,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12457,
+2,8,10218,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12458,
+2,8,10220,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12460,
+2,8,10222,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12461,
+2,8,10225,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12462,
+2,8,10228,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12463,
+2,8,10229,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12464,
+2,8,10233,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12465,
+2,8,10236,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12466,
+2,8,10240,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12467,
+2,8,10244,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12468,
+2,8,10245,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12469,
+2,8,10254,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12470,
+2,8,10255,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12471,
+2,8,10258,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12472,
diff --git 
a/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_issue_commits_for_ConvertIssueRepoCommits.csv
 
b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_issue_commits_for_ConvertIssueRepoCommits.csv
new file mode 100644
index 000000000..d4555dbb2
--- /dev/null
+++ 
b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_issue_commits_for_ConvertIssueRepoCommits.csv
@@ -0,0 +1,80 @@
+"created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark","connection_id","issue_id","commit_sha","commit_url"
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",1,"",2,10063,"8748a066cbaf67b15e86f2c636f9931347e987cf","https://gitlab.com/meri.co/vdev.co/-/commit/8748a066cbaf67b15e86f2c636f9931347e987cf";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",2,"",2,10064,"abc0892edaee00dd7ee268dbee71620407a29bca","https://gitlab.com/meri.co/vdev.co/-/commit/abc0892edaee00dd7ee268dbee71620407a29bca";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":3,""BoardId"":8}","_raw_jira_api_remotelinks",3,"",2,10064,"e6bde456807818c5c78d7b265964d6d48b653af6","https://gitlab.com/meri.co/vdev.co/-/commit/e6bde456807818c5c78d7b265964d6d48b653af6";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",4,"",2,10065,"8f91020bcf684c6ad07adfafa3d8a2f826686c42","https://gitlab.com/meri.co/vdev.co/-/commit/8f91020bcf684c6ad07adfafa3d8a2f826686c42";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",5,"",2,10066,"0dfe2e9ed88ad4e27f825d9b67d4d56ac983c5ef","https://gitlab.com/meri.co/vdev.co/-/commit/0dfe2e9ed88ad4e27f825d9b67d4d56ac983c5ef";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":1,""BoardId"":8}","_raw_jira_api_remotelinks",13,"",2,10139,"8993c04249e9d549e8950daec86717548c53c423","https://gitlab.com/meri.co/vdev.co/-/commit/8993c04249e9d549e8950daec86717548c53c423";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",19,"",2,10145,"07aa2ebed68e286dc51a7e0082031196a6135f74","https://gitlab.com/meri.co/vdev.co/-/commit/07aa2ebed68e286dc51a7e0082031196a6135f74";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":3}","_raw_jira_api_remotelinks",20,"",2,10145,"d70d6687e06304d9b6e0cb32b3f8c0f0928400f7","https://gitlab.com/meri.co/vdev.co/-/commit/d70d6687e06304d9b6e0cb32b3f8c0f0928400f7";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",21,"",2,10145,"ef5ab26111744f65f5191b247767a473c70d6c95","https://gitlab.com/meri.co/vdev.co/-/commit/ef5ab26111744f65f5191b247767a473c70d6c95";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":5}","_raw_jira_api_remotelinks",36,"",2,10159,"d28785ff09229ac9e3c6734f0c97466ab00eb4da","https://gitlab.com/meri.co/vdev.co/-/commit/d28785ff09229ac9e3c6734f0c97466ab00eb4da";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",38,"",2,10202,"0ab12c4d4064003602edceed900d1456b6209894","https://gitlab.com/meri.co/vdev.co/-/commit/0ab12c4d4064003602edceed900d1456b6209894";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",39,"",2,10203,"980e9fe7bc3e22a0409f7241a024eaf9c53680dd","https://gitlab.com/meri.co/vdev.co/-/commit/980e9fe7bc3e22a0409f7241a024eaf9c53680dd";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",40,"",2,10204,"f399421652f6ce0495d2c6bae66406cbeb68501f","https://gitlab.com/meri.co/vdev.co/-/commit/f399421652f6ce0495d2c6bae66406cbeb68501f";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",41,"",2,10205,"149ed453d5353850dee5c6112178f77480dcadfd","https://gitlab.com/meri.co/vdev.co/-/commit/149ed453d5353850dee5c6112178f77480dcadfd";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",42,"",2,10206,"8e95965d9aae9622b2ec8e5bd0ed5a30238d400e","https://gitlab.com/meri.co/vdev.co/-/commit/8e95965d9aae9622b2ec8e5bd0ed5a30238d400e";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",43,"",2,10207,"a65c311ee04f002840e22c8f9c8fcdfe00c792ee","https://gitlab.com/meri.co/vdev.co/-/commit/a65c311ee04f002840e22c8f9c8fcdfe00c792ee";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",44,"",2,10213,"f33cbc960a80075b30efe2daa32555f8bccfdc23","https://gitlab.com/meri.co/vdev.co/-/commit/f33cbc960a80075b30efe2daa32555f8bccfdc23";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",45,"",2,10218,"02e31edfe6036baf91a4924e77b83dc276c9fab6","https://gitlab.com/meri.co/vdev.co/-/commit/02e31edfe6036baf91a4924e77b83dc276c9fab6";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",46,"",2,10218,"34cf50c449a390754002df1e6942b485ef6f0211","https://gitlab.com/meri.co/vdev.co/-/commit/34cf50c449a390754002df1e6942b485ef6f0211";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",47,"",2,10220,"a148367d7987597b11386cd8e70ae3d721190ff3","https://gitlab.com/meri.co/vdev.co/-/commit/a148367d7987597b11386cd8e70ae3d721190ff3";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",48,"",2,10222,"fba5215ae3cf712e2bf282341a497ba4650f24eb","https://gitlab.com/meri.co/vdev.co/-/commit/fba5215ae3cf712e2bf282341a497ba4650f24eb";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",50,"",2,10225,"f53e78b4a3615d33bf8a8f60d9eaef18ac1364ba","https://gitlab.com/meri.co/vdev.co/-/commit/f53e78b4a3615d33bf8a8f60d9eaef18ac1364ba";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",51,"",2,10228,"59f7687f0206ce13071ba185ec506c2a8872ac64","https://gitlab.com/meri.co/vdev.co/-/commit/59f7687f0206ce13071ba185ec506c2a8872ac64";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",52,"",2,10229,"671af58d588adf6f9cf6ba80057b1488cc3021bd","https://gitlab.com/meri.co/vdev.co/-/commit/671af58d588adf6f9cf6ba80057b1488cc3021bd";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",60,"",2,10233,"14b62ef2c2b9f9a45d4d00ec489babfa88e8cfd5","https://gitlab.com/meri.co/vdev.co/-/commit/14b62ef2c2b9f9a45d4d00ec489babfa88e8cfd5";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",62,"",2,10233,"3748c4aaf1eabe8127a94d6b3c72365c594d6b72","https://gitlab.com/meri.co/vdev.co/-/commit/3748c4aaf1eabe8127a94d6b3c72365c594d6b72";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",58,"",2,10233,"3b23c7006da255946bd7ef08c854cde089160939","https://gitlab.com/meri.co/vdev.co/-/commit/3b23c7006da255946bd7ef08c854cde089160939";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",53,"",2,10233,"58e5c84befc02505477a142c56b501310af9408a","https://gitlab.com/meri.co/vdev.co/-/commit/58e5c84befc02505477a142c56b501310af9408a";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",55,"",2,10233,"60ba46213cc0ab0e3f02d7bb15bea47c13adfa80","https://gitlab.com/meri.co/vdev.co/-/commit/60ba46213cc0ab0e3f02d7bb15bea47c13adfa80";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",57,"",2,10233,"64fd3d264ddc9b84d927ef1ebad70386a4aa200a","https://gitlab.com/meri.co/vdev.co/-/commit/64fd3d264ddc9b84d927ef1ebad70386a4aa200a";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",59,"",2,10233,"b4c5198ecb3295599ff7390939a3428a12acad5f","https://gitlab.com/meri.co/vdev.co/-/commit/b4c5198ecb3295599ff7390939a3428a12acad5f";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",56,"",2,10233,"e2a829f16a9659bf88c5abf750873f083513bc02","https://gitlab.com/meri.co/vdev.co/-/commit/e2a829f16a9659bf88c5abf750873f083513bc02";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",54,"",2,10233,"f55cc8a695302ec5faa18ecc422b4c1c2b27bb6b","https://gitlab.com/meri.co/vdev.co/-/commit/f55cc8a695302ec5faa18ecc422b4c1c2b27bb6b";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",65,"",2,10236,"27d83e6f7d45a820b449555386374cd6af2b4a5d","https://gitlab.com/meri.co/vdev.co/-/commit/27d83e6f7d45a820b449555386374cd6af2b4a5d";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",66,"",2,10236,"2f0044cc78a62bcbf5f8f0e69cde1255cc6ab8bc","https://gitlab.com/meri.co/vdev.co/-/commit/2f0044cc78a62bcbf5f8f0e69cde1255cc6ab8bc";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",63,"",2,10236,"6c44dd89f6a9826dc6f832ce7c5707e8b3537e72","https://gitlab.com/meri.co/vdev.co/-/commit/6c44dd89f6a9826dc6f832ce7c5707e8b3537e72";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",64,"",2,10236,"b9922dba05eb24c8cf5f30a50d31aa2d1544e37c","https://gitlab.com/meri.co/vdev.co/-/commit/b9922dba05eb24c8cf5f30a50d31aa2d1544e37c";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",67,"",2,10240,"eb5985b7583a8de5bbb9defae79c29be20e246c9","https://gitlab.com/meri.co/vdev.co/-/commit/eb5985b7583a8de5bbb9defae79c29be20e246c9";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",68,"",2,10244,"9448fc7f7d89e36c8e9d5bc15070a0cc88f610eb","https://gitlab.com/meri.co/vdev.co/-/commit/9448fc7f7d89e36c8e9d5bc15070a0cc88f610eb";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",81,"",2,10245,"0c8003d3244509ca77b02f7806ccce2105a34235","https://gitlab.com/meri.co/vdev.co/-/commit/0c8003d3244509ca77b02f7806ccce2105a34235";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",83,"",2,10245,"15d98b3f066c667bdc533a42e0b88433a94f6306","https://gitlab.com/meri.co/vdev.co/-/commit/15d98b3f066c667bdc533a42e0b88433a94f6306";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",69,"",2,10245,"1a48756d4d9f23ad3e1d1175c31767c4c3501f7a","https://gitlab.com/meri.co/vdev.co/-/commit/1a48756d4d9f23ad3e1d1175c31767c4c3501f7a";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",85,"",2,10245,"22307892a6cddaafc40bd129b559bcb731beeabe","https://gitlab.com/meri.co/vdev.co/-/commit/22307892a6cddaafc40bd129b559bcb731beeabe";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",75,"",2,10245,"2307b8e839b38e5a1b4cb989320cfef9f36eb120","https://gitlab.com/meri.co/vdev.co/-/commit/2307b8e839b38e5a1b4cb989320cfef9f36eb120";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",90,"",2,10245,"23cf67b7b3b9352941094f77710935443cd85724","https://gitlab.com/meri.co/vdev.co/-/commit/23cf67b7b3b9352941094f77710935443cd85724";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",78,"",2,10245,"3fa323caafb7861402489e3055c57ed4558ffeb6","https://gitlab.com/meri.co/vdev.co/-/commit/3fa323caafb7861402489e3055c57ed4558ffeb6";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",72,"",2,10245,"5a5bfdbc5ed92dfecc7141f29dc7c077a5c68f0d","https://gitlab.com/meri.co/vdev.co/-/commit/5a5bfdbc5ed92dfecc7141f29dc7c077a5c68f0d";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",82,"",2,10245,"61b0bd3c2ef25aa6392717ab07a8b407654650f1","https://gitlab.com/meri.co/vdev.co/-/commit/61b0bd3c2ef25aa6392717ab07a8b407654650f1";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",86,"",2,10245,"6b1d2bb920d2a55ea9eda242d61cf31d8f831078","https://gitlab.com/meri.co/vdev.co/-/commit/6b1d2bb920d2a55ea9eda242d61cf31d8f831078";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",88,"",2,10245,"90574eecd6d016b070b21a0ae370bf78acc71aea","https://gitlab.com/meri.co/vdev.co/-/commit/90574eecd6d016b070b21a0ae370bf78acc71aea";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",73,"",2,10245,"91a1dde3b40a2e364aaa0230ac9771805ae51b13","https://gitlab.com/meri.co/vdev.co/-/commit/91a1dde3b40a2e364aaa0230ac9771805ae51b13";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",79,"",2,10245,"a61cb79f68a95e46ad625b949d7cda5c6b8f23f9","https://gitlab.com/meri.co/vdev.co/-/commit/a61cb79f68a95e46ad625b949d7cda5c6b8f23f9";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",89,"",2,10245,"b4a222c589fd7585d1337fecb3f841dfa575df34","https://gitlab.com/meri.co/vdev.co/-/commit/b4a222c589fd7585d1337fecb3f841dfa575df34";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",74,"",2,10245,"c1aae3eb5fddc2bcec27e1a7f0a7d0cc58532d9b","https://gitlab.com/meri.co/vdev.co/-/commit/c1aae3eb5fddc2bcec27e1a7f0a7d0cc58532d9b";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",87,"",2,10245,"cccc754c7fd22ba5b5d384b50ca13fb848760693","https://gitlab.com/meri.co/vdev.co/-/commit/cccc754c7fd22ba5b5d384b50ca13fb848760693";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",84,"",2,10245,"cf5352eabbf9054f82934f71699afc3afd19f49b","https://gitlab.com/meri.co/vdev.co/-/commit/cf5352eabbf9054f82934f71699afc3afd19f49b";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",80,"",2,10245,"dce6cce39f5db84223fbefd5898fb5bb3dee0872","https://gitlab.com/meri.co/vdev.co/-/commit/dce6cce39f5db84223fbefd5898fb5bb3dee0872";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",77,"",2,10245,"e51801c323245eb4ffa06cd6840885a3e875f998","https://gitlab.com/meri.co/vdev.co/-/commit/e51801c323245eb4ffa06cd6840885a3e875f998";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",70,"",2,10245,"f0123e5de6dc1f832adfcace6f2882fd7b42a7eb","https://gitlab.com/meri.co/vdev.co/-/commit/f0123e5de6dc1f832adfcace6f2882fd7b42a7eb";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",71,"",2,10245,"f6cc2639a9ca65045448eb2ad4434f09bfc956a2","https://gitlab.com/meri.co/vdev.co/-/commit/f6cc2639a9ca65045448eb2ad4434f09bfc956a2";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",76,"",2,10245,"fa9eacc59249179d1e88e6917673c3f8f3c1be4d","https://gitlab.com/meri.co/vdev.co/-/commit/fa9eacc59249179d1e88e6917673c3f8f3c1be4d";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",97,"",2,10254,"207bd0ad7e526354a3ccf01fc49c6d46631635ab","https://gitlab.com/meri.co/vdev.co/-/commit/207bd0ad7e526354a3ccf01fc49c6d46631635ab";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",92,"",2,10254,"48e2b22c92e53a92925930619cc2c7d9a3164b6b","https://gitlab.com/meri.co/vdev.co/-/commit/48e2b22c92e53a92925930619cc2c7d9a3164b6b";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",95,"",2,10254,"5d974f16941a4141fc204201bf44312b90ae4efc","https://gitlab.com/meri.co/vdev.co/-/commit/5d974f16941a4141fc204201bf44312b90ae4efc";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",93,"",2,10254,"846a273648d3aa3a358d76c0ee5a6f77c43b8833","https://gitlab.com/meri.co/vdev.co/-/commit/846a273648d3aa3a358d76c0ee5a6f77c43b8833";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",96,"",2,10254,"976bd42fbc2ccb9f0a464660926d0694998e1343","https://gitlab.com/meri.co/vdev.co/-/commit/976bd42fbc2ccb9f0a464660926d0694998e1343";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",98,"",2,10254,"dc33c42a35370a91bdccd207ba9821f430a0f0df","https://gitlab.com/meri.co/vdev.co/-/commit/dc33c42a35370a91bdccd207ba9821f430a0f0df";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",91,"",2,10254,"dc55075222445b8cd6bcc090179c7825fe5f8cf5","https://gitlab.com/meri.co/vdev.co/-/commit/dc55075222445b8cd6bcc090179c7825fe5f8cf5";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",94,"",2,10254,"fc26876aea9e9658ae10214bac94508b85ef41ce","https://gitlab.com/meri.co/vdev.co/-/commit/fc26876aea9e9658ae10214bac94508b85ef41ce";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",104,"",2,10255,"2bf4c9723d0e7e899c205c2f5e107f29df754f9b","https://gitlab.com/meri.co/vdev.co/-/commit/2bf4c9723d0e7e899c205c2f5e107f29df754f9b";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",103,"",2,10255,"5c45f15376d303f5b3b4577471d06b1139ce32e7","https://gitlab.com/meri.co/vdev.co/-/commit/5c45f15376d303f5b3b4577471d06b1139ce32e7";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",100,"",2,10255,"6d3592a0a70225a62af3152b4623e4b2caf7d85e","https://gitlab.com/meri.co/vdev.co/-/commit/6d3592a0a70225a62af3152b4623e4b2caf7d85e";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",99,"",2,10255,"73eddda7e14eceeabd98dd768ee37d19a0fbb80a","https://gitlab.com/meri.co/vdev.co/-/commit/73eddda7e14eceeabd98dd768ee37d19a0fbb80a";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",106,"",2,10255,"a802d5edf833b8fa70189783ebe21174ff333c69","https://gitlab.com/meri.co/vdev.co/-/commit/a802d5edf833b8fa70189783ebe21174ff333c69";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",102,"",2,10255,"a8cffff240ae813495d0fcf229664ff4d5661efa","https://gitlab.com/meri.co/vdev.co/-/commit/a8cffff240ae813495d0fcf229664ff4d5661efa";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",101,"",2,10255,"d7b0f04186e2f5aba14b9c543d4b02be2a6dd5c2","https://gitlab.com/meri.co/vdev.co/-/commit/d7b0f04186e2f5aba14b9c543d4b02be2a6dd5c2";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",105,"",2,10255,"dad016718d6a1f629b98df95b9021932e03a2877","https://gitlab.com/meri.co/vdev.co/-/commit/dad016718d6a1f629b98df95b9021932e03a2877";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",108,"",2,10258,"25988d15d2a977cbf440c6728e90b3cbfa667ad2","https://gitlab.com/meri.co/vdev.co/-/commit/25988d15d2a977cbf440c6728e90b3cbfa667ad2";
+"2022-10-25 08:36:38.628","2022-10-25 
08:36:38.628","{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_remotelinks",107,"",2,10258,"29380320f47ca4d687d29c8395931ca5d8a606e7","https://gitlab.com/meri.co/vdev.co/-/commit/29380320f47ca4d687d29c8395931ca5d8a606e7";
diff --git a/backend/plugins/jira/e2e/snapshot_tables/issue_repo_commits.csv 
b/backend/plugins/jira/e2e/snapshot_tables/issue_repo_commits.csv
new file mode 100644
index 000000000..7227a7d54
--- /dev/null
+++ b/backend/plugins/jira/e2e/snapshot_tables/issue_repo_commits.csv
@@ -0,0 +1,80 @@
+issue_id,repo_url,commit_sha,host,namespace,repo_name,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
+jira:JiraIssue:2:10063,https://gitlab.com/meri.co/vdev.co.git,8748a066cbaf67b15e86f2c636f9931347e987cf,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,1,
+jira:JiraIssue:2:10064,https://gitlab.com/meri.co/vdev.co.git,abc0892edaee00dd7ee268dbee71620407a29bca,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,2,
+jira:JiraIssue:2:10064,https://gitlab.com/meri.co/vdev.co.git,e6bde456807818c5c78d7b265964d6d48b653af6,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":3,""BoardId"":8}",_raw_jira_api_remotelinks,3,
+jira:JiraIssue:2:10065,https://gitlab.com/meri.co/vdev.co.git,8f91020bcf684c6ad07adfafa3d8a2f826686c42,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,4,
+jira:JiraIssue:2:10066,https://gitlab.com/meri.co/vdev.co.git,0dfe2e9ed88ad4e27f825d9b67d4d56ac983c5ef,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,5,
+jira:JiraIssue:2:10139,https://gitlab.com/meri.co/vdev.co.git,8993c04249e9d549e8950daec86717548c53c423,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":1,""BoardId"":8}",_raw_jira_api_remotelinks,13,
+jira:JiraIssue:2:10145,https://gitlab.com/meri.co/vdev.co.git,07aa2ebed68e286dc51a7e0082031196a6135f74,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,19,
+jira:JiraIssue:2:10145,https://gitlab.com/meri.co/vdev.co.git,d70d6687e06304d9b6e0cb32b3f8c0f0928400f7,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":3}",_raw_jira_api_remotelinks,20,
+jira:JiraIssue:2:10145,https://gitlab.com/meri.co/vdev.co.git,ef5ab26111744f65f5191b247767a473c70d6c95,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,21,
+jira:JiraIssue:2:10159,https://gitlab.com/meri.co/vdev.co.git,d28785ff09229ac9e3c6734f0c97466ab00eb4da,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":5}",_raw_jira_api_remotelinks,36,
+jira:JiraIssue:2:10202,https://gitlab.com/meri.co/vdev.co.git,0ab12c4d4064003602edceed900d1456b6209894,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,38,
+jira:JiraIssue:2:10203,https://gitlab.com/meri.co/vdev.co.git,980e9fe7bc3e22a0409f7241a024eaf9c53680dd,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,39,
+jira:JiraIssue:2:10204,https://gitlab.com/meri.co/vdev.co.git,f399421652f6ce0495d2c6bae66406cbeb68501f,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,40,
+jira:JiraIssue:2:10205,https://gitlab.com/meri.co/vdev.co.git,149ed453d5353850dee5c6112178f77480dcadfd,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,41,
+jira:JiraIssue:2:10206,https://gitlab.com/meri.co/vdev.co.git,8e95965d9aae9622b2ec8e5bd0ed5a30238d400e,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,42,
+jira:JiraIssue:2:10207,https://gitlab.com/meri.co/vdev.co.git,a65c311ee04f002840e22c8f9c8fcdfe00c792ee,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,43,
+jira:JiraIssue:2:10213,https://gitlab.com/meri.co/vdev.co.git,f33cbc960a80075b30efe2daa32555f8bccfdc23,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,44,
+jira:JiraIssue:2:10218,https://gitlab.com/meri.co/vdev.co.git,02e31edfe6036baf91a4924e77b83dc276c9fab6,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,45,
+jira:JiraIssue:2:10218,https://gitlab.com/meri.co/vdev.co.git,34cf50c449a390754002df1e6942b485ef6f0211,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,46,
+jira:JiraIssue:2:10220,https://gitlab.com/meri.co/vdev.co.git,a148367d7987597b11386cd8e70ae3d721190ff3,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,47,
+jira:JiraIssue:2:10222,https://gitlab.com/meri.co/vdev.co.git,fba5215ae3cf712e2bf282341a497ba4650f24eb,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,48,
+jira:JiraIssue:2:10225,https://gitlab.com/meri.co/vdev.co.git,f53e78b4a3615d33bf8a8f60d9eaef18ac1364ba,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,50,
+jira:JiraIssue:2:10228,https://gitlab.com/meri.co/vdev.co.git,59f7687f0206ce13071ba185ec506c2a8872ac64,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,51,
+jira:JiraIssue:2:10229,https://gitlab.com/meri.co/vdev.co.git,671af58d588adf6f9cf6ba80057b1488cc3021bd,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,52,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,14b62ef2c2b9f9a45d4d00ec489babfa88e8cfd5,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,60,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,3748c4aaf1eabe8127a94d6b3c72365c594d6b72,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,62,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,3b23c7006da255946bd7ef08c854cde089160939,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,58,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,58e5c84befc02505477a142c56b501310af9408a,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,53,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,60ba46213cc0ab0e3f02d7bb15bea47c13adfa80,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,55,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,64fd3d264ddc9b84d927ef1ebad70386a4aa200a,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,57,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,b4c5198ecb3295599ff7390939a3428a12acad5f,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,59,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,e2a829f16a9659bf88c5abf750873f083513bc02,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,56,
+jira:JiraIssue:2:10233,https://gitlab.com/meri.co/vdev.co.git,f55cc8a695302ec5faa18ecc422b4c1c2b27bb6b,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,54,
+jira:JiraIssue:2:10236,https://gitlab.com/meri.co/vdev.co.git,27d83e6f7d45a820b449555386374cd6af2b4a5d,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,65,
+jira:JiraIssue:2:10236,https://gitlab.com/meri.co/vdev.co.git,2f0044cc78a62bcbf5f8f0e69cde1255cc6ab8bc,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,66,
+jira:JiraIssue:2:10236,https://gitlab.com/meri.co/vdev.co.git,6c44dd89f6a9826dc6f832ce7c5707e8b3537e72,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,63,
+jira:JiraIssue:2:10236,https://gitlab.com/meri.co/vdev.co.git,b9922dba05eb24c8cf5f30a50d31aa2d1544e37c,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,64,
+jira:JiraIssue:2:10240,https://gitlab.com/meri.co/vdev.co.git,eb5985b7583a8de5bbb9defae79c29be20e246c9,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,67,
+jira:JiraIssue:2:10244,https://gitlab.com/meri.co/vdev.co.git,9448fc7f7d89e36c8e9d5bc15070a0cc88f610eb,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,68,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,0c8003d3244509ca77b02f7806ccce2105a34235,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,81,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,15d98b3f066c667bdc533a42e0b88433a94f6306,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,83,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,1a48756d4d9f23ad3e1d1175c31767c4c3501f7a,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,69,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,22307892a6cddaafc40bd129b559bcb731beeabe,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,85,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,2307b8e839b38e5a1b4cb989320cfef9f36eb120,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,75,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,23cf67b7b3b9352941094f77710935443cd85724,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,90,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,3fa323caafb7861402489e3055c57ed4558ffeb6,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,78,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,5a5bfdbc5ed92dfecc7141f29dc7c077a5c68f0d,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,72,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,61b0bd3c2ef25aa6392717ab07a8b407654650f1,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,82,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,6b1d2bb920d2a55ea9eda242d61cf31d8f831078,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,86,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,90574eecd6d016b070b21a0ae370bf78acc71aea,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,88,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,91a1dde3b40a2e364aaa0230ac9771805ae51b13,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,73,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,a61cb79f68a95e46ad625b949d7cda5c6b8f23f9,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,79,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,b4a222c589fd7585d1337fecb3f841dfa575df34,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,89,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,c1aae3eb5fddc2bcec27e1a7f0a7d0cc58532d9b,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,74,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,cccc754c7fd22ba5b5d384b50ca13fb848760693,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,87,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,cf5352eabbf9054f82934f71699afc3afd19f49b,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,84,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,dce6cce39f5db84223fbefd5898fb5bb3dee0872,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,80,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,e51801c323245eb4ffa06cd6840885a3e875f998,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,77,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,f0123e5de6dc1f832adfcace6f2882fd7b42a7eb,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,70,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,f6cc2639a9ca65045448eb2ad4434f09bfc956a2,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,71,
+jira:JiraIssue:2:10245,https://gitlab.com/meri.co/vdev.co.git,fa9eacc59249179d1e88e6917673c3f8f3c1be4d,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,76,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,207bd0ad7e526354a3ccf01fc49c6d46631635ab,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,97,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,48e2b22c92e53a92925930619cc2c7d9a3164b6b,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,92,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,5d974f16941a4141fc204201bf44312b90ae4efc,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,95,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,846a273648d3aa3a358d76c0ee5a6f77c43b8833,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,93,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,976bd42fbc2ccb9f0a464660926d0694998e1343,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,96,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,dc33c42a35370a91bdccd207ba9821f430a0f0df,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,98,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,dc55075222445b8cd6bcc090179c7825fe5f8cf5,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,91,
+jira:JiraIssue:2:10254,https://gitlab.com/meri.co/vdev.co.git,fc26876aea9e9658ae10214bac94508b85ef41ce,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,94,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,2bf4c9723d0e7e899c205c2f5e107f29df754f9b,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,104,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,5c45f15376d303f5b3b4577471d06b1139ce32e7,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,103,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,6d3592a0a70225a62af3152b4623e4b2caf7d85e,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,100,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,73eddda7e14eceeabd98dd768ee37d19a0fbb80a,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,99,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,a802d5edf833b8fa70189783ebe21174ff333c69,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,106,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,a8cffff240ae813495d0fcf229664ff4d5661efa,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,102,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,d7b0f04186e2f5aba14b9c543d4b02be2a6dd5c2,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,101,
+jira:JiraIssue:2:10255,https://gitlab.com/meri.co/vdev.co.git,dad016718d6a1f629b98df95b9021932e03a2877,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,105,
+jira:JiraIssue:2:10258,https://gitlab.com/meri.co/vdev.co.git,25988d15d2a977cbf440c6728e90b3cbfa667ad2,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,108,
+jira:JiraIssue:2:10258,https://gitlab.com/meri.co/vdev.co.git,29380320f47ca4d687d29c8395931ca5d8a606e7,gitlab.com,meri.co,vdev.co,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_remotelinks,107,
diff --git 
a/backend/plugins/jira/models/migrationscripts/20230223_add_repo_pattern.go 
b/backend/plugins/jira/models/migrationscripts/20230223_add_repo_pattern.go
new file mode 100644
index 000000000..9c4365f19
--- /dev/null
+++ b/backend/plugins/jira/models/migrationscripts/20230223_add_repo_pattern.go
@@ -0,0 +1,48 @@
+/*
+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.
+*/
+
+package migrationscripts
+
+import (
+       "encoding/json"
+
+       "github.com/apache/incubator-devlake/core/context"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/helpers/migrationhelper"
+)
+
+type JiraTransformationRule20230223 struct {
+       RemotelinkRepoPattern json.RawMessage 
`mapstructure:"remotelinkRepoPattern,omitempty" json:"remotelinkRepoPattern"`
+}
+
+func (JiraTransformationRule20230223) TableName() string {
+       return "_tool_jira_transformation_rules"
+}
+
+type addCommitRepoPattern struct{}
+
+func (script *addCommitRepoPattern) Up(basicRes context.BasicRes) errors.Error 
{
+       return migrationhelper.AutoMigrateTables(basicRes, 
&JiraTransformationRule20230223{})
+}
+
+func (*addCommitRepoPattern) Version() uint64 {
+       return 20230223112532
+}
+
+func (*addCommitRepoPattern) Name() string {
+       return "add remotelink_repo_pattern to _tool_jira_transformation_rules"
+}
diff --git a/backend/plugins/jira/models/migrationscripts/register.go 
b/backend/plugins/jira/models/migrationscripts/register.go
index be622cf81..780822d40 100644
--- a/backend/plugins/jira/models/migrationscripts/register.go
+++ b/backend/plugins/jira/models/migrationscripts/register.go
@@ -31,5 +31,6 @@ func All() []plugin.MigrationScript {
                new(addProjectName20221215),
                new(addJiraMultiAuth20230129),
                new(removeIssueStdStoryPoint),
+               new(addCommitRepoPattern),
        }
 }
diff --git a/backend/plugins/jira/models/transformation_rules.go 
b/backend/plugins/jira/models/transformation_rules.go
index f98ce4f4a..7e18dabe8 100644
--- a/backend/plugins/jira/models/transformation_rules.go
+++ b/backend/plugins/jira/models/transformation_rules.go
@@ -19,6 +19,9 @@ package models
 
 import (
        "encoding/json"
+       "regexp"
+
+       "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/models/common"
 )
 
@@ -28,9 +31,32 @@ type JiraTransformationRule struct {
        EpicKeyField               string          
`mapstructure:"epicKeyField,omitempty" json:"epicKeyField" 
gorm:"type:varchar(255)"`
        StoryPointField            string          
`mapstructure:"storyPointField,omitempty" json:"storyPointField" 
gorm:"type:varchar(255)"`
        RemotelinkCommitShaPattern string          
`mapstructure:"remotelinkCommitShaPattern,omitempty" 
json:"remotelinkCommitShaPattern" gorm:"type:varchar(255)"`
+       RemotelinkRepoPattern      json.RawMessage 
`mapstructure:"remotelinkRepoPattern,omitempty" json:"remotelinkRepoPattern"`
        TypeMappings               json.RawMessage 
`mapstructure:"typeMappings,omitempty" json:"typeMappings"`
 }
 
-func (JiraTransformationRule) TableName() string {
+func (r *JiraTransformationRule) TableName() string {
        return "_tool_jira_transformation_rules"
 }
+
+func (r *JiraTransformationRule) VerifyRegexp() errors.Error {
+       var err error
+       if r.RemotelinkCommitShaPattern != "" {
+               _, err = regexp.Compile(r.RemotelinkCommitShaPattern)
+               if err != nil {
+                       return errors.Convert(err)
+               }
+       }
+       var repoPatterns []string
+       err = json.Unmarshal(r.RemotelinkRepoPattern, &repoPatterns)
+       if err != nil {
+               return errors.Convert(err)
+       }
+       for _, pattern := range repoPatterns {
+               _, err = regexp.Compile(pattern)
+               if err != nil {
+                       return errors.Convert(err)
+               }
+       }
+       return nil
+}
diff --git a/backend/plugins/jira/tasks/issue_repo_commit_convertor.go 
b/backend/plugins/jira/tasks/issue_repo_commit_convertor.go
index d9222939a..08695ec89 100644
--- a/backend/plugins/jira/tasks/issue_repo_commit_convertor.go
+++ b/backend/plugins/jira/tasks/issue_repo_commit_convertor.go
@@ -18,6 +18,11 @@ limitations under the License.
 package tasks
 
 import (
+       "net/url"
+       "path"
+       "reflect"
+       "regexp"
+
        "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
        
"github.com/apache/incubator-devlake/core/models/domainlayer/crossdomain"
@@ -25,8 +30,6 @@ import (
        "github.com/apache/incubator-devlake/core/plugin"
        "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/apache/incubator-devlake/plugins/jira/models"
-       "reflect"
-       "regexp"
 )
 
 var ConvertIssueRepoCommitsMeta = plugin.SubTaskMeta{
@@ -53,6 +56,16 @@ func ConvertIssueRepoCommits(taskCtx plugin.SubTaskContext) 
errors.Error {
        if err != nil {
                return errors.Default.Wrap(err, "regexp Compile 
commitRepoUrlPattern failed")
        }
+       var commitRepoUrlRegexps []*regexp.Regexp
+       if tr := data.Options.TransformationRules; tr != nil {
+               for _, s := range tr.RemotelinkRepoPattern {
+                       pattern, e := regexp.Compile(s)
+                       if e != nil {
+                               return errors.Convert(e)
+                       }
+                       commitRepoUrlRegexps = append(commitRepoUrlRegexps, 
pattern)
+               }
+       }
 
        clause := []dal.Clause{
                dal.From("_tool_jira_issue_commits jic"),
@@ -83,7 +96,6 @@ func ConvertIssueRepoCommits(taskCtx plugin.SubTaskContext) 
errors.Error {
                InputRowType: reflect.TypeOf(models.JiraIssueCommit{}),
                Input:        cursor,
                Convert: func(inputRow interface{}) ([]interface{}, 
errors.Error) {
-                       var result []interface{}
                        issueCommit := inputRow.(*models.JiraIssueCommit)
                        item := &crossdomain.IssueRepoCommit{
                                IssueId:   
issueIdGenerator.Generate(connectionId, issueCommit.IssueId),
@@ -95,8 +107,8 @@ func ConvertIssueRepoCommits(taskCtx plugin.SubTaskContext) 
errors.Error {
                                        item.RepoUrl = groups[1]
                                }
                        }
-                       result = append(result, item)
-                       return result, nil
+                       refineIssueRepoCommit(item, commitRepoUrlRegexps, 
issueCommit.CommitUrl)
+                       return []interface{}{item}, nil
                },
        })
        if err != nil {
@@ -105,3 +117,25 @@ func ConvertIssueRepoCommits(taskCtx 
plugin.SubTaskContext) errors.Error {
 
        return converter.Execute()
 }
+
+func refineIssueRepoCommit(item *crossdomain.IssueRepoCommit, repoPatterns 
[]*regexp.Regexp, commitUrl string) *crossdomain.IssueRepoCommit {
+       u, err := url.Parse(commitUrl)
+       if err != nil {
+               return item
+       }
+       item.Host = u.Host
+       for _, pattern := range repoPatterns {
+               if pattern.MatchString(commitUrl) {
+                       group := pattern.FindStringSubmatch(commitUrl)
+                       if len(group) == 4 {
+                               item.Namespace = group[1]
+                               item.RepoName = group[2]
+                               item.CommitSha = group[3]
+                               u.Path = path.Join(item.Namespace, 
item.RepoName+".git")
+                               item.RepoUrl = u.String()
+                               break
+                       }
+               }
+       }
+       return item
+}
diff --git a/backend/plugins/jira/tasks/issue_repo_commit_convertor_test.go 
b/backend/plugins/jira/tasks/issue_repo_commit_convertor_test.go
new file mode 100644
index 000000000..c9310ea56
--- /dev/null
+++ b/backend/plugins/jira/tasks/issue_repo_commit_convertor_test.go
@@ -0,0 +1,131 @@
+/*
+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.
+*/
+
+package tasks
+
+import (
+       
"github.com/apache/incubator-devlake/core/models/domainlayer/crossdomain"
+       "reflect"
+       "regexp"
+       "testing"
+)
+
+func Test_refineIssueRepoCommit(t *testing.T) {
+       type args struct {
+               item         *crossdomain.IssueRepoCommit
+               repoPatterns []*regexp.Regexp
+               commitUrl    string
+       }
+       tests := []struct {
+               name string
+               args args
+               want *crossdomain.IssueRepoCommit
+       }{
+               {
+                       "bitbucket server",
+                       args{
+                               item:         
&crossdomain.IssueRepoCommit{IssueId: "abc123", CommitSha: 
"1e23e7f1a0cb539c7408c38e5a37de3bc836bc94"},
+                               repoPatterns: 
[]*regexp.Regexp{regexp.MustCompile("https://example.com/bitbucket/projects/(?P<namespace>[^/]+)/repos/(?P<repo_name>[^/]+)/commits/(?P<commit_sha>\\w{40})")},
+                               commitUrl:    
"https://example.com/bitbucket/projects/PROJECTNAME/repos/ui_jira/commits/1e23e7f1a0cb539c7408c38e5a37de3bc836bc94";,
+                       },
+                       &crossdomain.IssueRepoCommit{
+                               IssueId:   "abc123",
+                               CommitSha: 
"1e23e7f1a0cb539c7408c38e5a37de3bc836bc94",
+                               RepoUrl:   
"https://example.com/PROJECTNAME/ui_jira.git";,
+                               Host:      "example.com",
+                               Namespace: "PROJECTNAME",
+                               RepoName:  "ui_jira",
+                       },
+               },
+               {
+                       "bitbucket cloud",
+                       args{
+                               item:         
&crossdomain.IssueRepoCommit{IssueId: "abc123", CommitSha: 
"fef8d697fbb9a2b336be6fa2e2848f585c86a622"},
+                               repoPatterns: 
[]*regexp.Regexp{regexp.MustCompile(`https://bitbucket.org/(?P<namespace>[^/]+)/(?P<repo_name>[^/]+)/commits/(?P<commit_sha>\w{40})`)},
+                               commitUrl:    
"https://bitbucket.org/mynamespace/incubator-devlake/commits/fef8d697fbb9a2b336be6fa2e2848f585c86a622";,
+                       },
+                       &crossdomain.IssueRepoCommit{
+                               IssueId:   "abc123",
+                               CommitSha: 
"fef8d697fbb9a2b336be6fa2e2848f585c86a622",
+                               RepoUrl:   
"https://bitbucket.org/mynamespace/incubator-devlake.git";,
+                               Host:      "bitbucket.org",
+                               Namespace: "mynamespace",
+                               RepoName:  "incubator-devlake",
+                       },
+               },
+               {
+                       "GitHub",
+                       args{
+                               item:         
&crossdomain.IssueRepoCommit{IssueId: "abc123", CommitSha: 
"a7c6550b6a273af36e9850291a52601d3dca367c"},
+                               repoPatterns: 
[]*regexp.Regexp{regexp.MustCompile(`https://github.com/(?P<namespace>[^/]+)/(?P<repo_name>[^/]+)/commit/(?P<commit_sha>\w{40})`)},
+                               commitUrl:    
"https://github.com/apache/incubator-devlake/commit/a7c6550b6a273af36e9850291a52601d3dca367c";,
+                       },
+                       &crossdomain.IssueRepoCommit{
+                               IssueId:   "abc123",
+                               CommitSha: 
"a7c6550b6a273af36e9850291a52601d3dca367c",
+                               RepoUrl:   
"https://github.com/apache/incubator-devlake.git";,
+                               Host:      "github.com",
+                               Namespace: "apache",
+                               RepoName:  "incubator-devlake",
+                       },
+               },
+               {
+                       "GitLab cloud",
+                       args{
+                               item:         
&crossdomain.IssueRepoCommit{IssueId: "abc123", CommitSha: 
"050baf4575caf069275f5fa14db9ad4a21a79883"},
+                               repoPatterns: 
[]*regexp.Regexp{regexp.MustCompile(`https://gitlab.com/(?P<namespace>\S+/\S+)/(?P<repo_name>\w+)/-/commit/(?P<commit_sha>\w{40})`)},
+                               commitUrl:    
"https://gitlab.com/namespace1/namespace2/myrepo/-/commit/050baf4575caf069275f5fa14db9ad4a21a79883";,
+                       },
+                       &crossdomain.IssueRepoCommit{
+                               IssueId:   "abc123",
+                               CommitSha: 
"050baf4575caf069275f5fa14db9ad4a21a79883",
+                               RepoUrl:   
"https://gitlab.com/namespace1/namespace2/myrepo.git";,
+                               Host:      "gitlab.com",
+                               Namespace: "namespace1/namespace2",
+                               RepoName:  "myrepo",
+                       },
+               },
+               {
+                       "GitLab cloud",
+                       args{
+                               item: &crossdomain.IssueRepoCommit{IssueId: 
"abc123", CommitSha: "a802d5edf833b8fa70189783ebe21174ff333c69"},
+                               repoPatterns: []*regexp.Regexp{
+                                       
//regexp.MustCompile(`https://bitbucket.org/(?P<namespace>[^/]+)/(?P<repo_name>[^/]+)/commits/(?P<commit_sha>\w{40})`),
+                                       
regexp.MustCompile(`https://gitlab.com/(?P<namespace>\S+)/(?P<repo_name>\S+)/-/commit/(?P<commit_sha>\w{40})`),
+                                       
//regexp.MustCompile(`https://github.com/(?P<namespace>[^/]+)/(?P<repo_name>[^/]+)/commit/(?P<commit_sha>\w{40})`),
+                               },
+                               commitUrl: 
"https://gitlab.com/meri.co/vdev.co/-/commit/a802d5edf833b8fa70189783ebe21174ff333c69";,
+                       },
+                       &crossdomain.IssueRepoCommit{
+                               IssueId:   "abc123",
+                               CommitSha: 
"a802d5edf833b8fa70189783ebe21174ff333c69",
+                               RepoUrl:   
"https://gitlab.com/meri.co/vdev.co.git";,
+                               Host:      "gitlab.com",
+                               Namespace: "meri.co",
+                               RepoName:  "vdev.co",
+                       },
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       if got := refineIssueRepoCommit(tt.args.item, 
tt.args.repoPatterns, tt.args.commitUrl); !reflect.DeepEqual(got, tt.want) {
+                               t.Logf("%+v", got)
+                               t.Errorf("refineIssueRepoCommit() = %v, want 
%v", got, tt.want)
+                       }
+               })
+       }
+}
diff --git a/backend/plugins/jira/tasks/task_data.go 
b/backend/plugins/jira/tasks/task_data.go
index 5ac5a9d13..d58a680a8 100644
--- a/backend/plugins/jira/tasks/task_data.go
+++ b/backend/plugins/jira/tasks/task_data.go
@@ -45,34 +45,31 @@ type JiraTransformationRule struct {
        EpicKeyField               string       `json:"epicKeyField"`
        StoryPointField            string       `json:"storyPointField"`
        RemotelinkCommitShaPattern string       
`json:"remotelinkCommitShaPattern"`
+       RemotelinkRepoPattern      []string     `json:"remotelinkRepoPattern"`
        TypeMappings               TypeMappings `json:"typeMappings"`
 }
 
-func (r *JiraTransformationRule) ToDb() (rule *models.JiraTransformationRule, 
error2 errors.Error) {
+func (r *JiraTransformationRule) ToDb() (*models.JiraTransformationRule, 
errors.Error) {
        blob, err := json.Marshal(r.TypeMappings)
        if err != nil {
                return nil, errors.Default.Wrap(err, "error marshaling 
TypeMappings")
        }
-       return &models.JiraTransformationRule{
+       remotelinkRepoPattern, err := json.Marshal(r.RemotelinkRepoPattern)
+       if err != nil {
+               return nil, errors.Default.Wrap(err, "error marshaling 
RemotelinkRepoPattern")
+       }
+       rule := &models.JiraTransformationRule{
                Name:                       r.Name,
                EpicKeyField:               r.EpicKeyField,
                StoryPointField:            r.StoryPointField,
                RemotelinkCommitShaPattern: r.RemotelinkCommitShaPattern,
+               RemotelinkRepoPattern:      remotelinkRepoPattern,
                TypeMappings:               blob,
-       }, nil
-}
-func (r *JiraTransformationRule) FromDb(rule *models.JiraTransformationRule) 
(*JiraTransformationRule, errors.Error) {
-       mappings := make(map[string]TypeMapping)
-       err := json.Unmarshal(rule.TypeMappings, &mappings)
-       if err != nil {
-               return nil, errors.Default.Wrap(err, "error marshaling 
TypeMappings")
        }
-       r.Name = rule.Name
-       r.EpicKeyField = rule.EpicKeyField
-       r.StoryPointField = rule.StoryPointField
-       r.RemotelinkCommitShaPattern = rule.RemotelinkCommitShaPattern
-       r.TypeMappings = mappings
-       return r, nil
+       if err1 := rule.VerifyRegexp(); err1 != nil {
+               return nil, err1
+       }
+       return rule, nil
 }
 
 func MakeTransformationRules(rule models.JiraTransformationRule) 
(*JiraTransformationRule, errors.Error) {
@@ -81,11 +78,17 @@ func MakeTransformationRules(rule 
models.JiraTransformationRule) (*JiraTransform
        if err != nil {
                return nil, errors.Default.Wrap(err, "unable to unmarshal the 
typeMapping")
        }
+       var remotelinkRepoPattern []string
+       err = json.Unmarshal(rule.RemotelinkRepoPattern, &remotelinkRepoPattern)
+       if err != nil {
+               return nil, errors.Default.Wrap(err, "error unMarshaling 
RemotelinkRepoPattern")
+       }
        result := &JiraTransformationRule{
                Name:                       rule.Name,
                EpicKeyField:               rule.EpicKeyField,
                StoryPointField:            rule.StoryPointField,
                RemotelinkCommitShaPattern: rule.RemotelinkCommitShaPattern,
+               RemotelinkRepoPattern:      remotelinkRepoPattern,
                TypeMappings:               typeMapping,
        }
        return result, nil

Reply via email to