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

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


The following commit(s) were added to refs/heads/main by this push:
     new 092b8953c fix(gitlab): add missing commit_updated_at column used by MR 
incremental filter (#9046)
092b8953c is described below

commit 092b8953ce3fdbaace1215ba01dc5ee450eff9a7
Author: Dirk <[email protected]>
AuthorDate: Fri Aug 14 02:40:19 2026 +0200

    fix(gitlab): add missing commit_updated_at column used by MR incremental 
filter (#9046)
    
    #8959 changed `GetMergeRequestsIterator` to filter merge requests by
    `GREATEST(gmr.gitlab_updated_at, COALESCE(gmr.commit_updated_at, 
gmr.gitlab_updated_at))`,
    but `commit_updated_at` does not exist on `_tool_gitlab_merge_requests`:
    there is neither a model field nor a migration script, and nothing ever
    writes the value.
    
    As a result every incremental "Collect MR Notes" run (the only caller that
    passes a stateful collector; the MR commit collector passes nil) aborts 
with:
    
        Error 1054 (42S22): Unknown column 'gmr.commit_updated_at' in 'where 
clause'
    
    This makes the intended behaviour actually work instead of reverting it:
    
    * add `CommitUpdatedAt` to `GitlabMergeRequest` plus a migration script
    * maintain the column in the MR commit extractor, setting it to the latest
      authored date of the MR's commits, so MRs that received new commits 
without
      their own `updated_at` being bumped (e.g. force-pushes) are picked up 
again
    * the collector filter is unchanged and now resolves against a real column
    
    Verified on MySQL 8 and PostgreSQL 14 (extractor UPDATE and the GREATEST
    filter produce identical results on both).
---
 .../20260812_add_mr_commit_updated_at.go           | 50 ++++++++++++++++++++++
 .../gitlab/models/migrationscripts/register.go     |  1 +
 backend/plugins/gitlab/models/mr.go                |  5 +++
 .../plugins/gitlab/tasks/mr_commit_extractor.go    | 32 +++++++++++++-
 backend/plugins/gitlab/tasks/shared.go             |  3 +-
 5 files changed, 89 insertions(+), 2 deletions(-)

diff --git 
a/backend/plugins/gitlab/models/migrationscripts/20260812_add_mr_commit_updated_at.go
 
b/backend/plugins/gitlab/models/migrationscripts/20260812_add_mr_commit_updated_at.go
new file mode 100644
index 000000000..b88b66546
--- /dev/null
+++ 
b/backend/plugins/gitlab/models/migrationscripts/20260812_add_mr_commit_updated_at.go
@@ -0,0 +1,50 @@
+/*
+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 (
+       "time"
+
+       "github.com/apache/incubator-devlake/core/context"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
+)
+
+var _ plugin.MigrationScript = (*addMrCommitUpdatedAt)(nil)
+
+type mrCommitUpdatedAt struct {
+       CommitUpdatedAt *time.Time
+}
+
+func (mrCommitUpdatedAt) TableName() string {
+       return "_tool_gitlab_merge_requests"
+}
+
+type addMrCommitUpdatedAt struct{}
+
+func (*addMrCommitUpdatedAt) Up(basicRes context.BasicRes) errors.Error {
+       return 
errors.Convert(basicRes.GetDal().AutoMigrate(&mrCommitUpdatedAt{}))
+}
+
+func (*addMrCommitUpdatedAt) Version() uint64 {
+       return 20260812000001
+}
+
+func (*addMrCommitUpdatedAt) Name() string {
+       return "gitlab: add commit_updated_at to merge requests"
+}
diff --git a/backend/plugins/gitlab/models/migrationscripts/register.go 
b/backend/plugins/gitlab/models/migrationscripts/register.go
index e684c9530..65fa6f41b 100644
--- a/backend/plugins/gitlab/models/migrationscripts/register.go
+++ b/backend/plugins/gitlab/models/migrationscripts/register.go
@@ -54,5 +54,6 @@ func All() []plugin.MigrationScript {
                new(addIsChildToPipelines240906),
                new(addPrSizeExcludedFileExtensions),
                new(addAdditionsDeletionsToMr),
+               new(addMrCommitUpdatedAt),
        }
 }
diff --git a/backend/plugins/gitlab/models/mr.go 
b/backend/plugins/gitlab/models/mr.go
index 4d8e63ec5..9d8acae8d 100644
--- a/backend/plugins/gitlab/models/mr.go
+++ b/backend/plugins/gitlab/models/mr.go
@@ -53,6 +53,11 @@ type GitlabMergeRequest struct {
        Component        string `gorm:"type:varchar(255)"`
        Additions        *int   `gorm:"comment:Lines added in this MR diff 
only"`
        Deletions        *int   `gorm:"comment:Lines deleted in this MR diff 
only"`
+       // CommitUpdatedAt holds the most recent authored date among the 
commits of this MR.
+       // GitLab does not bump `updated_at` for every push (e.g. 
force-pushes), so this
+       // timestamp is used in addition to GitlabUpdatedAt to detect MRs that 
changed
+       // during incremental collection.
+       CommitUpdatedAt *time.Time `gorm:"comment:Latest authored date of the 
commits belonging to this MR"`
        common.NoPKModel
 }
 
diff --git a/backend/plugins/gitlab/tasks/mr_commit_extractor.go 
b/backend/plugins/gitlab/tasks/mr_commit_extractor.go
index 7a94f9fee..506f0321b 100644
--- a/backend/plugins/gitlab/tasks/mr_commit_extractor.go
+++ b/backend/plugins/gitlab/tasks/mr_commit_extractor.go
@@ -104,7 +104,37 @@ func ExtractApiMergeRequestsCommits(subtaskCtx 
plugin.SubTaskContext) errors.Err
                return err
        }
 
-       return extractor.Execute()
+       if err := extractor.Execute(); err != nil {
+               return err
+       }
+
+       return updateMrCommitUpdatedAt(subtaskCtx)
+}
+
+// updateMrCommitUpdatedAt refreshes 
`_tool_gitlab_merge_requests.commit_updated_at` with the
+// latest authored date of the MR's commits. GitLab does not always bump the 
MR's own
+// `updated_at` when commits are pushed (e.g. force-pushes), so subtasks 
collecting
+// MR sub-entities incrementally would otherwise skip those MRs.
+func updateMrCommitUpdatedAt(subtaskCtx plugin.SubTaskContext) errors.Error {
+       data := subtaskCtx.GetData().(*GitlabTaskData)
+       return subtaskCtx.GetDal().Exec(`
+               UPDATE _tool_gitlab_merge_requests AS gmr
+               SET commit_updated_at = (
+                       SELECT MAX(gmc.commit_authored_date)
+                       FROM _tool_gitlab_mr_commits gmc
+                       WHERE gmc.connection_id = gmr.connection_id
+                         AND gmc.merge_request_id = gmr.gitlab_id
+               )
+               WHERE gmr.connection_id = ?
+                 AND gmr.project_id = ?
+                 AND EXISTS (
+                       SELECT 1
+                       FROM _tool_gitlab_mr_commits gmc
+                       WHERE gmc.connection_id = gmr.connection_id
+                         AND gmc.merge_request_id = gmr.gitlab_id
+                 )`,
+               data.Options.ConnectionId, data.Options.ProjectId,
+       )
 }
 
 // Convert the API response to our DB model instance
diff --git a/backend/plugins/gitlab/tasks/shared.go 
b/backend/plugins/gitlab/tasks/shared.go
index eac88b66f..3462e2cb0 100644
--- a/backend/plugins/gitlab/tasks/shared.go
+++ b/backend/plugins/gitlab/tasks/shared.go
@@ -198,7 +198,8 @@ func GetMergeRequestsIterator(taskCtx 
plugin.SubTaskContext, apiCollector *api.S
                        // Filter by the LATER of gitlab_updated_at or 
commit_updated_at.
                        // Using only gitlab_updated_at misses MRs where new 
commits were pushed
                        // without the MR itself being updated (e.g. 
force-pushed commits).
-                       // COALESCE handles MRs with no recorded 
commit_updated_at.
+                       // commit_updated_at is maintained by the MR commit 
extractor and is NULL
+                       // for MRs without collected commits, hence the 
COALESCE.
                        clauses = append(clauses, dal.Where(
                                `GREATEST(gmr.gitlab_updated_at, 
COALESCE(gmr.commit_updated_at, gmr.gitlab_updated_at)) > ?`,
                                *apiCollector.GetSince(),

Reply via email to