Repository: runatlantis/atlantis (language: go)

Issue:
[Gitlab] - Empty changes list
<!--- Please keep this note for the community --->

### Community Note

* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request. Searching for pre-existing feature requests helps us consolidate datapoints for identical requirements into a single place, thank you!
* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request.
* If you are interested in working on this issue or have submitted a pull request, please leave a comment.

<!--- Thank you for keeping this note for the community --->

---

<!---
When filing a bug, please include the following headings if possible.
Any example text in this template can be deleted.
--->

### Overview of the Issue

<!---
Please describe the issue you are having and how you encountered the problem.
--->

### This is a self hosted Gitlab enterprise cluster on `15.9.2`

I am seeing an initial empty `changes` response from Gitlab after the web-hook triggers the [GetModifiedFiles](https://github.com/runatlantis/atlantis/blob/main/server/events/vcs/gitlab_client.go#L111) function.

The initial Query to the `/changes` endpoint seems to have the possibility of returning an empty list of changes once triggered after the initial web-hook. However If i add a quick sleep into this before we actually query  the `/changes` endpoint the data is returned correctly.

To illustrate this point I have modified the Atlantis code and created a custom build that prints out the response body after the web-hook trigger event.

```json
{
	"id": 8312,
	"iid": 102,
	"target_branch": "main",
	"source_branch": "TestBranch",
	"project_id": 3771,
	"title": "Update <somefile>.yaml",
	"state": "opened",
	"created_at": "2023-03-14T13:43:17.895Z",
	"updated_at": "2023-03-14T13:43:17.895Z",
	"upvotes": 0,
	"downvotes": 0,
	"author": {
		"id": 194,
		"username": "<Me>",
		"name": "<Me>",
		"state": "active",
		"created_at": null,
		"avatar_url": "<Generic avatar_url>",
		"web_url": "<Gitlab URL>"
	},
	"assignee": null,
	"assignees": [],
	"reviewers": [],
	"source_project_id": 3771,
	"target_project_id": 3771,
	"labels": "",
	"description": "",
	"draft": false,
	"work_in_progress": false,
	"milestone": null,
	"merge_when_pipeline_succeeds": false,
	"detailed_merge_status": "checking",
	"merge_error": "",
	"merged_by": null,
	"merged_at": null,
	"closed_by": null,
	"closed_at": null,
	"subscribed": false,
	"sha": "3f382318c8ce9b0b606682c7e5a77753b6e157d8",
	"merge_commit_sha": "",
	"squash_commit_sha": "",
	"user_notes_count": 0,
	"changes_count": "",
	"should_remove_source_branch": false,
	"force_remove_source_branch": true,
	"allow_collaboration": false,
	"web_url": "<repo-url>/102",
	"references": {
		"short": "!102",
		"relative": "!102",
		"full": "<repo-url>!102"
	},
	"discussion_locked": false,
	"changes": [],
	"user": {
		"can_merge": true
	},
	"time_stats": {
		"human_time_estimate": "",
		"human_total_time_spent": "",
		"time_estimate": 0,
		"total_time_spent": 0
	},
	"squash": false,
	"pipeline": null,
	"head_pipeline": null,
	"diff_refs": {
		"base_sha": "",
		"head_sha": "",
		"start_sha": ""
	},
	"diverged_commits_count": 0,
	"rebase_in_progress": false,
	"approvals_before_merge": 0,
	"reference": "!102",
	"first_contribution": false,
	"task_completion_status": {
		"count": 0,
		"completed_count": 0
	},
	"has_conflicts": false,
	"blocking_discussions_resolved": true,
	"overflow": false,
	"merge_status": "checking"
}
```

Please take note of the fact that `changes` is an empty slice and  `changes_count` is actually an empty string.

Now now the I sleep for 2 seconds and try the query again. 


```json
{
	"id": 8312,
	"iid": 102,
	"project_id": 3771,
	"title": "Update <somefile>.yaml",
	"description": "",
	"state": "opened",
	"created_at": "2023-03-14T13:43:17.895Z",
	"updated_at": "2023-03-14T13:43:59.978Z",
	"merged_by": null,
	"merge_user": null,
	"merged_at": null,
	"closed_by": null,
	"closed_at": null,
	"target_branch": "main",
	"source_branch": "TestBranch",
	"user_notes_count": 0,
	"upvotes": 0,
	"downvotes": 0,
	"author": {
		"id": 194,
		"username": "<Me>",
		"name": "<Me>",
		"state": "active",
		"created_at": null,
		"avatar_url": "<Generic avatar_url>",
		"web_url": "<Gitlab URL>"
	},
	"assignees": [],
	"assignee": null,
	"reviewers": [],
	"source_project_id": 3771,
	"target_project_id": 3771,
	"labels": [],
	"draft": false,
	"work_in_progress": false,
	"milestone": null,
	"merge_when_pipeline_succeeds": false,
	"merge_status": "can_be_merged",
	"detailed_merge_status": "not_approved",
	"sha": "3f382318c8ce9b0b606682c7e5a77753b6e157d8",
	"merge_commit_sha": null,
	"squash_commit_sha": null,
	"discussion_locked": null,
	"should_remove_source_branch": null,
	"force_remove_source_branch": true,
	"reference": "!102",
	"references": {
		"short": "!102",
		"relative": "!102",
		"full": "<repo-url>!102"
	},
	"web_url": "<repo-url>/102",
	"time_stats": {
		"time_estimate": 0,
		"total_time_spent": 0,
		"human_time_estimate": null,
		"human_total_time_spent": null
	},
	"squash": false,
	"squash_on_merge": false,
	"task_completion_status": {
		"count": 0,
		"completed_count": 0
	},
	"has_conflicts": false,
	"blocking_discussions_resolved": true,
	"approvals_before_merge": null,
	"subscribed": true,
	"changes_count": "1",
	"pipeline": null,
	"head_pipeline": null,
	"diff_refs": {
		"base_sha": "3fc27008bb70a83e3e5d79fce5b6f7e0b3f02a09",
		"head_sha": "3f382318c8ce9b0b606682c7e5a77753b6e157d8",
		"start_sha": "3fc27008bb70a83e3e5d79fce5b6f7e0b3f02a09"
	},
	"merge_error": null,
	"user": {
		"can_merge": true
	},
	"changes": [{
		"diff": "<removed file diff>",
		"new_path": "<somefile>.yaml",
		"old_path": "<somefile>.yaml",
		"a_mode": "100644",
		"b_mode": "100644",
		"new_file": false,
		"renamed_file": false,
		"deleted_file": false
	}],
	"overflow": false
}
```

You can see that the API is returning different results for `changes_count ` and `changes`. It's almost like cert parts of the change have not been fully flushed through the system before we make the initial query.

This feels like a bug with the latest Gitlab patch. We have opened a ticket with Gitlab on this. When we get a response I will update this issue accordingly. 

However I think it should also be possible to try and detect against situations like this. It appears that when a change request does not have any changes the `changes_count ` field is actually `null` instead of an empty string. 

```"changes_count": null,```

### Reproduction Steps

<!--- 
In order to effectively and quickly resolve the issue, please provide exact steps that allow us the reproduce the problem. If no steps are provided, then it will likely take longer to get the issue resolved.
--->


### Logs

<!---
Provide log files from Atlantis server

logs can be retrieved from the deployment or from atlantis comments by adding `--debug` such as `atlantis plan --debug`

<details>
  <summary>Logs</summary>

```
log output
```

</details>
--->


### Environment details

<!---
If not already included, please provide the following:

- Atlantis version:
- Deployment method: ecs/eks/helm/tf module
- If not running the latest Atlantis version have you tried to reproduce this issue on the latest version: 
- Atlantis flags:

Atlantis server-side config file:

```yaml
# config file
```

Repo `atlantis.yaml` file:

```yaml
# config file
```

Any other information you can provide about the environment/deployment (efs/nfs, aws/gcp, k8s/fargate, etc)
--->


### Additional Context

<!---
Additional context on the problem. Docs, links to blogs, or other material that lead you to discover this issue or were helpful in troubleshooting the issue. 

Use a bulleted list to link to tickets
--->




Discussion / hints:
@a-chartier 

We hit this same exact issue, with our self hosted gitlab.  The MR's come in with `no change` which breaks everything.  We also have an open issue with Gitlab.


I see you can `atlantis plan`  to kick it off again.


@a-chartier 

They opened a bug for what its worth: https://gitlab.com/gitlab-org/gitlab/-/issues/397698
@jseiser 
Sorry I wasn't around much this weekend, we submitted the bug report and all the logs to them once I figured out what was going on, I was hoping to avoid having other people waste time on this.

But it seems like they don't consider it a bug, but it's a breaking change. Since they don't consider this a bug I think some changes may need to be made on the Atlantis side to check until we have a change count that isn't none.


Other older issue that the author linked to

https://gitlab.com/gitlab-org/gitlab/-/issues/386562

Unsure what the easy solution is here unless we were to delay each gitlab call to the endpoint by 5 seconds... Which seems like a gross solution.

Hopefully they fix the regression in 397698 before implementing the new endpoint referenced in 386562.
@nitrocode 

Yeah I don't like the idea of a hard sleep, I was thinking that in the event that a change comes back with a `null` or empty `changes_count` Atlantis could just retry the the call.

Since `changes_count` should either be `0` or `N` for it to be a valid response. 
@nitrocode 
@a-chartier 

Its pretty clear they broke their stuff during a minor version release, and are now creating MR's to reflect they did so in their docs.  The current open issue to resolve this, hasn't even been assigned in 2 months. Talking to our PM, it doesnt appear that Gitlab is going to do anything to `fix` this on their end, since they have decided its not broken.



> Unsure what the easy solution is here unless we were to delay each gitlab call to the endpoint by 5 seconds... Which seems like a gross solution.

Im not sure there is any other choice, but to query/back off/query until its not ""


We are trying to get a Pull Request together done on this, we do have a Go Lang dev, but he has no concept/idea of atlantis so trying to get this moving.

Fix patch:
diff --git a/server/events/vcs/gitlab_client.go b/server/events/vcs/gitlab_client.go
index 8b0ded3866..84ef44fcbb 100644
--- a/server/events/vcs/gitlab_client.go
+++ b/server/events/vcs/gitlab_client.go
@@ -41,6 +41,10 @@ type GitlabClient struct {
 	Client *gitlab.Client
 	// Version is set to the server version.
 	Version *version.Version
+	// PollingInterval is the time between successive polls, where applicable.
+	PollingInterval time.Duration
+	// PollingInterval is the total duration for which to poll, where applicable.
+	PollingTimeout time.Duration
 }
 
 // commonMarkSupported is a version constraint that is true when this version of
@@ -53,7 +57,10 @@ var gitlabClientUnderTest = false
 
 // NewGitlabClient returns a valid GitLab client.
 func NewGitlabClient(hostname string, token string, logger logging.SimpleLogging) (*GitlabClient, error) {
-	client := &GitlabClient{}
+	client := &GitlabClient{
+		PollingInterval: time.Second,
+		PollingTimeout:  time.Second * 30,
+	}
 
 	// Create the client differently depending on the base URL.
 	if hostname == "gitlab.com" {
@@ -123,10 +130,21 @@ func (g *GitlabClient) GetModifiedFiles(repo models.Repo, pull models.PullReques
 		if err != nil {
 			return nil, err
 		}
+		resp := new(gitlab.Response)
 		mr := new(gitlab.MergeRequest)
-		resp, err := g.Client.Do(req, mr)
-		if err != nil {
-			return nil, err
+		pollingStart := time.Now()
+		for {
+			resp, err = g.Client.Do(req, mr)
+			if err != nil {
+				return nil, err
+			}
+			if mr.ChangesCount != "" {
+				break
+			}
+			if time.Since(pollingStart) > g.PollingTimeout {
+				return nil, errors.Errorf("giving up polling %q after %s", apiURL, g.PollingTimeout.String())
+			}
+			time.Sleep(g.PollingInterval)
 		}
 
 		for _, f := range mr.Changes {


Test patch:
diff --git a/server/events/vcs/gitlab_client_test.go b/server/events/vcs/gitlab_client_test.go
index 1396060f59..3159f6757b 100644
--- a/server/events/vcs/gitlab_client_test.go
+++ b/server/events/vcs/gitlab_client_test.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"testing"
+	"time"
 
 	version "github.com/hashicorp/go-version"
 	"github.com/runatlantis/atlantis/server/events/models"
@@ -105,6 +106,65 @@ func TestGitlabClient_SupportsCommonMark(t *testing.T) {
 	}
 }
 
+func TestGitlabClient_GetModifiedFiles(t *testing.T) {
+	cases := []struct {
+		attempts int
+	}{
+		{1}, {2}, {3},
+	}
+
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("Gitlab returns MR changes after %d attempts", c.attempts), func(t *testing.T) {
+			numAttempts := 0
+			testServer := httptest.NewServer(
+				http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					switch r.RequestURI {
+					case "/api/v4/projects/lkysow%2Fatlantis-example/merge_requests/8312/changes?page=1&per_page=100":
+						w.WriteHeader(200)
+						numAttempts += 1
+						if numAttempts < c.attempts {
+							w.Write([]byte(changesPending)) // nolint: errcheck
+							t.Logf("returning changesPending for attempt %d", numAttempts)
+							return
+						}
+						t.Logf("returning changesAvailable for attempt %d", numAttempts)
+						w.Write([]byte(changesAvailable)) // nolint: errcheck
+					default:
+						t.Errorf("got unexpected request at %q", r.RequestURI)
+						http.Error(w, "not found", http.StatusNotFound)
+					}
+				}))
+
+			internalClient, err := gitlab.NewClient("token", gitlab.WithBaseURL(testServer.URL))
+			Ok(t, err)
+			client := &GitlabClient{
+				Client:          internalClient,
+				Version:         nil,
+				PollingInterval: time.Second * 0,
+				PollingTimeout:  time.Second * 10,
+			}
+
+			filenames, err := client.GetModifiedFiles(
+				models.Repo{
+					FullName: "lkysow/atlantis-example",
+					Owner:    "lkysow",
+					Name:     "atlantis-example",
+				},
+				models.PullRequest{
+					Num: 8312,
+					BaseRepo: models.Repo{
+						FullName: "lkysow/atlantis-example",
+						Owner:    "lkysow",
+						Name:     "atlantis-example",
+					},
+				})
+			Ok(t, err)
+			Equals(t, []string{"somefile.yaml"}, filenames)
+		})
+	}
+
+}
+
 func TestGitlabClient_MergePull(t *testing.T) {
 	cases := []struct {
 		description string
@@ -351,3 +411,5 @@ func TestGitlabClient_MarkdownPullLink(t *testing.T) {
 var mergeSuccess = `{"id":22461274,"iid":13,"project_id":4580910,"title":"Update main.tf","description":"","state":"merged","created_at":"2019-01-15T18:27:29.375Z","updated_at":"2019-01-25T17:28:01.437Z","merged_by":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"merged_at":"2019-01-25T17:28:01.459Z","closed_by":null,"closed_at":null,"target_branch":"patch-1","source_branch":"patch-1-merger","upvotes":0,"downvotes":0,"author":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"assignee":null,"source_project_id":4580910,"target_project_id":4580910,"labels":[],"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"detailed_merge_status":"mergeable","sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha":"c9b336f1c71d3e64810b8cfa2abcfab232d6bff6","user_notes_count":0,"discussion_locked":null,"should_remove_source_branch":null,"force_remove_source_branch":false,"web_url":"https://gitlab.com/lkysow/atlantis-example/merge_requests/13","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":false,"subscribed":true,"changes_count":"1","latest_build_started_at":null,"latest_build_finished_at":null,"first_deployed_to_production_at":null,"pipeline":null,"diff_refs":{"base_sha":"67cb91d3f6198189f433c045154a885784ba6977","head_sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","start_sha":"67cb91d3f6198189f433c045154a885784ba6977"},"merge_error":null,"approvals_before_merge":null}`
 var pipelineSuccess = `{"id": 22461274,"iid": 13,"project_id": 4580910,"title": "Update main.tf","description": "","state": "opened","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","merged_by": null,"merged_at": null,"closed_by": null,"closed_at": null,"target_branch": "patch-1","source_branch": "patch-1-merger","user_notes_count": 0,"upvotes": 0,"downvotes": 0,"author": {"id": 1755902,"name": "Luke Kysow","username": "lkysow","state": "active","avatar_url": "https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url": "https://gitlab.com/lkysow"},"assignee": null,"reviewers": [],"source_project_id": 4580910,"target_project_id": 4580910,"labels": [],"work_in_progress": false,"milestone": null,"merge_when_pipeline_succeeds": false,"detailed_merge_status": "mergeable","sha": "cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha": null,"squash_commit_sha": null,"discussion_locked": null,"should_remove_source_branch": null,"force_remove_source_branch": true,"reference": "!13","references": {"short": "!13","relative": "!13","full": "lkysow/atlantis-example!13"},"web_url": "https://gitlab.com/lkysow/atlantis-example/merge_requests/13","time_stats": {"time_estimate": 0,"total_time_spent": 0,"human_time_estimate": null,"human_total_time_spent": null},"squash": true,"task_completion_status": {"count": 0,"completed_count": 0},"has_conflicts": false,"blocking_discussions_resolved": true,"approvals_before_merge": null,"subscribed": false,"changes_count": "1","latest_build_started_at": "2019-01-15T18:27:29.375Z","latest_build_finished_at": "2019-01-25T17:28:01.437Z","first_deployed_to_production_at": null,"pipeline": {"id": 488598,"sha": "67cb91d3f6198189f433c045154a885784ba6977","ref": "patch-1-merger","status": "success","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","web_url": "https://gitlab.com/lkysow/atlantis-example/-/pipelines/488598"},"head_pipeline": {"id": 488598,"sha": "67cb91d3f6198189f433c045154a885784ba6977","ref": "patch-1-merger","status": "success","created_at": "2019-01-15T18:27:29.375Z","updated_at": "2019-01-25T17:28:01.437Z","web_url": "https://gitlab.com/lkysow/atlantis-example/-/pipelines/488598","before_sha": "0000000000000000000000000000000000000000","tag": false,"yaml_errors": null,"user": {"id": 1755902,"name": "Luke Kysow","username": "lkysow","state": "active","avatar_url": "https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\u0026d=identicon","web_url": "https://gitlab.com/lkysow"},"started_at": "2019-01-15T18:27:29.375Z","finished_at": "2019-01-25T17:28:01.437Z","committed_at": null,"duration": 31,"coverage": null,"detailed_status": {"icon": "status_success","text": "passed","label": "passed","group": "success","tooltip": "passed","has_details": true,"details_path": "/lkysow/atlantis-example/-/pipelines/488598","illustration": null,"favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"}},"diff_refs": {"base_sha": "67cb91d3f6198189f433c045154a885784ba6977","head_sha": "cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","start_sha": "67cb91d3f6198189f433c045154a885784ba6977"},"merge_error": null,"first_contribution": false,"user": {"can_merge": true}}`
 var projectSuccess = `{"id": 4580910,"description": "","name": "atlantis-example","name_with_namespace": "lkysow / atlantis-example","path": "atlantis-example","path_with_namespace": "lkysow/atlantis-example","created_at": "2018-04-30T13:44:28.367Z","default_branch": "patch-1","tag_list": [],"ssh_url_to_repo": "git@gitlab.com:lkysow/atlantis-example.git","http_url_to_repo": "https://gitlab.com/lkysow/atlantis-example.git","web_url": "https://gitlab.com/lkysow/atlantis-example","readme_url": "https://gitlab.com/lkysow/atlantis-example/-/blob/main/README.md","avatar_url": "https://gitlab.com/uploads/-/system/project/avatar/4580910/avatar.png","forks_count": 0,"star_count": 7,"last_activity_at": "2021-06-29T21:10:43.968Z","namespace": {"id": 1,"name": "lkysow","path": "lkysow","kind": "group","full_path": "lkysow","parent_id": 1,"avatar_url": "/uploads/-/system/group/avatar/1651/platform.png","web_url": "https://gitlab.com/groups/lkysow"},"_links": {"self": "https://gitlab.com/api/v4/projects/4580910","issues": "https://gitlab.com/api/v4/projects/4580910/issues","merge_requests": "https://gitlab.com/api/v4/projects/4580910/merge_requests","repo_branches": "https://gitlab.com/api/v4/projects/4580910/repository/branches","labels": "https://gitlab.com/api/v4/projects/4580910/labels","events": "https://gitlab.com/api/v4/projects/4580910/events","members": "https://gitlab.com/api/v4/projects/4580910/members"},"packages_enabled": false,"empty_repo": false,"archived": false,"visibility": "private","resolve_outdated_diff_discussions": false,"container_registry_enabled": false,"container_expiration_policy": {"cadence": "1d","enabled": false,"keep_n": 10,"older_than": "90d","name_regex": ".*","name_regex_keep": null,"next_run_at": "2021-05-01T13:44:28.397Z"},"issues_enabled": true,"merge_requests_enabled": true,"wiki_enabled": false,"jobs_enabled": true,"snippets_enabled": true,"service_desk_enabled": false,"service_desk_address": null,"can_create_merge_request_in": true,"issues_access_level": "private","repository_access_level": "enabled","merge_requests_access_level": "enabled","forking_access_level": "enabled","wiki_access_level": "disabled","builds_access_level": "enabled","snippets_access_level": "enabled","pages_access_level": "private","operations_access_level": "disabled","analytics_access_level": "enabled","emails_disabled": null,"shared_runners_enabled": true,"lfs_enabled": false,"creator_id": 818,"import_status": "none","import_error": null,"open_issues_count": 0,"runners_token": "1234456","ci_default_git_depth": 50,"ci_forward_deployment_enabled": true,"public_jobs": true,"build_git_strategy": "fetch","build_timeout": 3600,"auto_cancel_pending_pipelines": "enabled","build_coverage_regex": null,"ci_config_path": "","shared_with_groups": [],"only_allow_merge_if_pipeline_succeeds": true,"allow_merge_on_skipped_pipeline": false,"restrict_user_defined_variables": false,"request_access_enabled": true,"only_allow_merge_if_all_discussions_are_resolved": true,"remove_source_branch_after_merge": true,"printing_merge_request_link_enabled": true,"merge_method": "merge","suggestion_commit_message": "","auto_devops_enabled": false,"auto_devops_deploy_strategy": "continuous","autoclose_referenced_issues": true,"repository_storage": "default","approvals_before_merge": 0,"mirror": false,"external_authorization_classification_label": null,"marked_for_deletion_at": null,"marked_for_deletion_on": null,"requirements_enabled": false,"compliance_frameworks": [],"permissions": {"project_access": null,"group_access": {"access_level": 50,"notification_level": 3}}}`
+var changesPending = `{"id":8312,"iid":102,"target_branch":"main","source_branch":"TestBranch","project_id":3771,"title":"Update somefile.yaml","state":"opened","created_at":"2023-03-14T13:43:17.895Z","updated_at":"2023-03-14T13:43:17.895Z","upvotes":0,"downvotes":0,"author":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"assignee":null,"assignees":[],"reviewers":[],"source_project_id":3771,"target_project_id":3771,"labels":"","description":"","draft":false,"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"detailed_merge_status":"checking","merge_error":"","merged_by":null,"merged_at":null,"closed_by":null,"closed_at":null,"subscribed":false,"sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha":"","squash_commit_sha":"","user_notes_count":0,"changes_count":"","should_remove_source_branch":false,"force_remove_source_branch":true,"allow_collaboration":false,"web_url":"https://gitlab.com/lkysow/atlantis-example/merge_requests/13","references":{"short":"!13","relative":"!13","full":"lkysow/atlantis-example!13"},"discussion_locked":false,"changes":[],"user":{"can_merge":true},"time_stats":{"human_time_estimate":"","human_total_time_spent":"","time_estimate":0,"total_time_spent":0},"squash":false,"pipeline":null,"head_pipeline":null,"diff_refs":{"base_sha":"","head_sha":"","start_sha":""},"diverged_commits_count":0,"rebase_in_progress":false,"approvals_before_merge":0,"reference":"!13","first_contribution":false,"task_completion_status":{"count":0,"completed_count":0},"has_conflicts":false,"blocking_discussions_resolved":true,"overflow":false,"merge_status":"checking"}`
+var changesAvailable = `{"id":8312,"iid":102,"target_branch":"main","source_branch":"TestBranch","project_id":3771,"title":"Update somefile.yaml","state":"opened","created_at":"2023-03-14T13:43:17.895Z","updated_at":"2023-03-14T13:43:59.978Z","upvotes":0,"downvotes":0,"author":{"id":1755902,"name":"Luke Kysow","username":"lkysow","state":"active","avatar_url":"https://secure.gravatar.com/avatar/25fd57e71590fe28736624ff24d41c5f?s=80\\u0026d=identicon","web_url":"https://gitlab.com/lkysow"},"assignee":null,"assignees":[],"reviewers":[],"source_project_id":3771,"target_project_id":3771,"labels":[],"description":"","draft":false,"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"detailed_merge_status":"not_approved","merge_error":"","merged_by":null,"merged_at":null,"closed_by":null,"closed_at":null,"subscribed":false,"sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","merge_commit_sha":null,"squash_commit_sha":null,"user_notes_count":0,"changes_count":"1","should_remove_source_branch":null,"force_remove_source_branch":true,"allow_collaboration":false,"web_url":"https://gitlab.com/lkysow/atlantis-example/merge_requests/13","references":{"short":"!13","relative":"!13","full":"lkysow/atlantis-example!13"},"discussion_locked":null,"changes":[{"old_path":"somefile.yaml","new_path":"somefile.yaml","a_mode":"100644","b_mode":"100644","diff":"--- a/somefile.yaml\\ +++ b/somefile.yaml\\ @@ -1 +1 @@\\ -gud\\ +good","new_file":false,"renamed_file":false,"deleted_file":false}],"user":{"can_merge":true},"time_stats":{"human_time_estimate":null,"human_total_time_spent":null,"time_estimate":0,"total_time_spent":0},"squash":false,"pipeline":null,"head_pipeline":null,"diff_refs":{"base_sha":"67cb91d3f6198189f433c045154a885784ba6977","head_sha":"cb86d70f464632bdfbe1bb9bc0f2f9d847a774a0","start_sha":"67cb91d3f6198189f433c045154a885784ba6977"},"approvals_before_merge":null,"reference":"!13","task_completion_status":{"count":0,"completed_count":0},"has_conflicts":false,"blocking_discussions_resolved":true,"overflow":false,"merge_status":"can_be_merged"}`