diff --git a/static/images/integrations/azuredevops/001.png b/static/images/integrations/azuredevops/001.png
new file mode 100644
index 0000000000..f20fb2197d
Binary files /dev/null and b/static/images/integrations/azuredevops/001.png differ
diff --git a/static/images/integrations/bot_avatars/azuredevops.png b/static/images/integrations/bot_avatars/azuredevops.png
new file mode 100644
index 0000000000..019e9ee0ec
Binary files /dev/null and b/static/images/integrations/bot_avatars/azuredevops.png differ
diff --git a/static/images/integrations/logos/azuredevops.svg b/static/images/integrations/logos/azuredevops.svg
new file mode 100644
index 0000000000..aba8103277
Binary files /dev/null and b/static/images/integrations/logos/azuredevops.svg differ
diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py
index b17f049e4f..5c3ebb41ef 100644
--- a/zerver/lib/integrations.py
+++ b/zerver/lib/integrations.py
@@ -334,6 +334,7 @@ WEBHOOK_INTEGRATIONS: List[WebhookIntegration] = [
WebhookIntegration("ansibletower", ["deployment"], display_name="Ansible Tower"),
WebhookIntegration("appfollow", ["customer-support"], display_name="AppFollow"),
WebhookIntegration("appveyor", ["continuous-integration"], display_name="AppVeyor"),
+ WebhookIntegration("azuredevops", ["version-control"], display_name="AzureDevOps"),
WebhookIntegration("beanstalk", ["version-control"], stream_name="commits"),
WebhookIntegration("basecamp", ["project-management"]),
WebhookIntegration("beeminder", ["misc"], display_name="Beeminder"),
@@ -690,6 +691,7 @@ DOC_SCREENSHOT_CONFIG: Dict[str, List[BaseScreenshotConfig]] = {
"ansibletower": [ScreenshotConfig("job_successful_multiple_hosts.json")],
"appfollow": [ScreenshotConfig("review.json")],
"appveyor": [ScreenshotConfig("appveyor_build_success.json")],
+ "azuredevops": [ScreenshotConfig("code_push.json")],
"basecamp": [ScreenshotConfig("doc_active.json")],
"beanstalk": [
ScreenshotConfig("git_multiple.json", use_basic_auth=True, payload_as_query_param=True)
diff --git a/zerver/webhooks/azuredevops/__init__.py b/zerver/webhooks/azuredevops/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/zerver/webhooks/azuredevops/doc.md b/zerver/webhooks/azuredevops/doc.md
new file mode 100644
index 0000000000..515f087f12
--- /dev/null
+++ b/zerver/webhooks/azuredevops/doc.md
@@ -0,0 +1,28 @@
+Get Azure Devops notifications in Zulip!
+
+1. {!create-stream.md!}
+
+1. {!create-bot-construct-url.md!}
+
+ {!git-webhook-url-with-branches.md!}
+
+1. Go to your project on Azure DevOps and click on the **Project
+ settings** in the bottom left corner. Select **Service
+ hooks**. Click on **Create subscription**. Select **Web hooks** and
+ then **Next**.
+
+1. Select the events you would like to receive notifications for and
+ then click **Next**. This integration supports the following
+ events:
+ * Code pushed
+ * Pull request created
+ * Pull request updated
+ * Pull request merge attempted
+
+1. Set **URL** to the URL constructed above. Ensure that **Resource
+ details to send** and **Detailed messages to send** are set to
+ **All**. Click **Finish**.
+
+{!congrats.md!}
+
+![](/static/images/integrations/azuredevops/001.png)
diff --git a/zerver/webhooks/azuredevops/fixtures/code_pull_request__merge_attempted.json b/zerver/webhooks/azuredevops/fixtures/code_pull_request__merge_attempted.json
new file mode 100644
index 0000000000..b50d4d8119
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_pull_request__merge_attempted.json
@@ -0,0 +1,114 @@
+{
+ "subscriptionId": "4f1eccb6-04ff-45dd-90aa-713078e5c395",
+ "notificationId": 5,
+ "id": "e41a8834-0875-4fa1-ace0-ba656e630af9",
+ "eventType": "git.pullrequest.merged",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Merge attempted for pull request 4 (Add 4th PR) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/",
+ "html": "Merge attempted for pull request 4 (Add 4th PR) in test-zulip",
+ "markdown": "Merge attempted for [pull request 4](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/4) (Add 4th PR) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)"
+ },
+ "detailedMessage": {
+ "text": "Merge attempted for pull request 4 (Add 4th PR) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/",
+ "html": "Merge attempted for pull request 4 (Add 4th PR) in test-zulip",
+ "markdown": "Merge attempted for [pull request 4](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/4) (Add 4th PR) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)"
+ },
+ "resource": {
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "revision": 11,
+ "visibility": "private",
+ "lastUpdateTime": "2022-07-17T05:01:05.987Z"
+ },
+ "size": 7840,
+ "remoteUrl": "https://ttchong@dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "sshUrl": "git@ssh.dev.azure.com:v3/ttchong/test-zulip/test-zulip",
+ "webUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "isDisabled": false
+ },
+ "pullRequestId": 4,
+ "codeReviewId": 4,
+ "status": "active",
+ "createdBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "creationDate": "2022-07-30T07:47:15.091534Z",
+ "title": "Add 4th PR",
+ "sourceRefName": "refs/heads/preprod",
+ "targetRefName": "refs/heads/main",
+ "mergeStatus": "conflicts",
+ "isDraft": false,
+ "mergeId": "697a791b-ab1a-48bf-910f-df958081a45c",
+ "lastMergeSourceCommit": {
+ "commitId": "885adddfa29eeeb8a0c9448b6744d2cfe02b1ebf",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/885adddfa29eeeb8a0c9448b6744d2cfe02b1ebf"
+ },
+ "lastMergeTargetCommit": {
+ "commitId": "6c86f973c0cd02726af8a5da074795745927e0d2",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/6c86f973c0cd02726af8a5da074795745927e0d2"
+ },
+ "reviewers": [
+ {
+ "reviewerUrl": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/4/reviewers/107e08c7-2725-675e-a1b0-281729035ea6",
+ "vote": 10,
+ "hasDeclined": false,
+ "isFlagged": false,
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6"
+ }
+ ],
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/4",
+ "_links": {
+ "web": {
+ "href": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/4"
+ },
+ "statuses": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/4/statuses"
+ }
+ },
+ "supportsIterations": true,
+ "artifactId": "vstfs:///Git/PullRequestId/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2%2f98f0ce59-a912-43d5-96d2-bc0942a03f7b%2f4"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-30T07:48:03.2511337Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_pull_request__merged.json b/zerver/webhooks/azuredevops/fixtures/code_pull_request__merged.json
new file mode 100644
index 0000000000..191d1d9ba2
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_pull_request__merged.json
@@ -0,0 +1,135 @@
+{
+ "subscriptionId": "4f1eccb6-04ff-45dd-90aa-713078e5c395",
+ "notificationId": 8,
+ "id": "d4cbc5f3-be4a-4fa6-80df-bfad83e313c5",
+ "eventType": "git.pullrequest.merged",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Merge attempted for pull request 1 (Add PR request) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/",
+ "html": "Merge attempted for pull request 1 (Add PR request) in test-zulip",
+ "markdown": "Merge attempted for [pull request 1](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1) (Add PR request) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)"
+ },
+ "detailedMessage": {
+ "text": "Merge attempted for pull request 1 (Add PR request) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/\r\nAdd PR request\r\n",
+ "html": "Merge attempted for pull request 1 (Add PR request) in test-zulip
Add PR request
",
+ "markdown": "Merge attempted for [pull request 1](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1) (Add PR request) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)\r\nAdd PR request\r\n"
+ },
+ "resource": {
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "revision": 11,
+ "visibility": "private",
+ "lastUpdateTime": "2022-07-17T05:01:05.987Z"
+ },
+ "size": 7840,
+ "remoteUrl": "https://ttchong@dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "sshUrl": "git@ssh.dev.azure.com:v3/ttchong/test-zulip/test-zulip",
+ "webUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "isDisabled": false
+ },
+ "pullRequestId": 1,
+ "codeReviewId": 1,
+ "status": "completed",
+ "createdBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "creationDate": "2022-07-24T08:02:50.160355Z",
+ "closedDate": "2022-07-30T07:55:03.5006647Z",
+ "title": "Add PR request",
+ "description": "Add PR request",
+ "sourceRefName": "refs/heads/dev",
+ "targetRefName": "refs/heads/main",
+ "mergeStatus": "succeeded",
+ "isDraft": false,
+ "mergeId": "3cb082bf-62fd-4ff9-81f2-bea41241eadb",
+ "lastMergeSourceCommit": {
+ "commitId": "179f0b4e4a9318d1907402662b0a416550926ebb",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/179f0b4e4a9318d1907402662b0a416550926ebb"
+ },
+ "lastMergeTargetCommit": {
+ "commitId": "6c86f973c0cd02726af8a5da074795745927e0d2",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/6c86f973c0cd02726af8a5da074795745927e0d2"
+ },
+ "lastMergeCommit": {
+ "commitId": "b2606c193460441311f4d31f4729589c77f5efe0",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-30T07:55:02Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-30T07:55:02Z"
+ },
+ "comment": "Merged PR 1: Add PR request",
+ "commentTruncated": true,
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b2606c193460441311f4d31f4729589c77f5efe0"
+ },
+ "reviewers": [],
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/1",
+ "_links": {
+ "web": {
+ "href": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1"
+ },
+ "statuses": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/1/statuses"
+ }
+ },
+ "completionOptions": {
+ "mergeCommitMessage": "Merged PR 1: Add PR request\n\nAdd PR request",
+ "mergeStrategy": "noFastForward",
+ "transitionWorkItems": true,
+ "autoCompleteIgnoreConfigIds": []
+ },
+ "supportsIterations": true,
+ "completionQueueTime": "2022-07-30T07:55:02.6159917Z",
+ "closedBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "artifactId": "vstfs:///Git/PullRequestId/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2%2f98f0ce59-a912-43d5-96d2-bc0942a03f7b%2f1"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-30T07:55:09.816217Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_pull_request__opened.json b/zerver/webhooks/azuredevops/fixtures/code_pull_request__opened.json
new file mode 100644
index 0000000000..1a9dbbc4f7
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_pull_request__opened.json
@@ -0,0 +1,113 @@
+{
+ "subscriptionId": "a0e7f493-8c02-498d-b9ee-86a8612bc74e",
+ "notificationId": 1,
+ "id": "d1e96858-24cd-4064-9e08-0c55db2ff3fa",
+ "eventType": "git.pullrequest.created",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki created pull request 1 (Add PR request) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/",
+ "html": "Yuro Itaki created pull request 1 (Add PR request) in test-zulip",
+ "markdown": "Yuro Itaki created [pull request 1](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1) (Add PR request) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki created pull request 1 (Add PR request) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/\r\nAdd PR request\r\n",
+ "html": "Yuro Itaki created pull request 1 (Add PR request) in test-zulipAdd PR request
",
+ "markdown": "Yuro Itaki created [pull request 1](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1) (Add PR request) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)\r\nAdd PR request\r\n"
+ },
+ "resource": {
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "revision": 11,
+ "visibility": "private",
+ "lastUpdateTime": "2022-07-17T05:01:05.987Z"
+ },
+ "size": 4425,
+ "remoteUrl": "https://ttchong@dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "sshUrl": "git@ssh.dev.azure.com:v3/ttchong/test-zulip/test-zulip",
+ "webUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "isDisabled": false
+ },
+ "pullRequestId": 1,
+ "codeReviewId": 1,
+ "status": "active",
+ "createdBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "creationDate": "2022-07-24T08:02:50.160355Z",
+ "title": "Add PR request",
+ "description": "Add PR request",
+ "sourceRefName": "refs/heads/dev",
+ "targetRefName": "refs/heads/main",
+ "mergeStatus": "succeeded",
+ "isDraft": false,
+ "mergeId": "3cb082bf-62fd-4ff9-81f2-bea41241eadb",
+ "lastMergeSourceCommit": {
+ "commitId": "8b913c8e0d0492952bd215da12df9a422182e9c5",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/8b913c8e0d0492952bd215da12df9a422182e9c5"
+ },
+ "lastMergeTargetCommit": {
+ "commitId": "0929a3404b39f6e39076a640779b2c1c961e19b5",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/0929a3404b39f6e39076a640779b2c1c961e19b5"
+ },
+ "lastMergeCommit": {
+ "commitId": "4d1292958374b3311a517429d6160f7fc434de86",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-24T08:02:50Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-24T08:02:50Z"
+ },
+ "comment": "Merge pull request 1 from dev into main",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/4d1292958374b3311a517429d6160f7fc434de86"
+ },
+ "reviewers": [],
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/1",
+ "_links": {
+ "web": {
+ "href": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1"
+ },
+ "statuses": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/1/statuses"
+ }
+ },
+ "supportsIterations": true,
+ "artifactId": "vstfs:///Git/PullRequestId/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2%2f98f0ce59-a912-43d5-96d2-bc0942a03f7b%2f1"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-24T08:02:56.9717413Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_pull_request__opened_without_description.json b/zerver/webhooks/azuredevops/fixtures/code_pull_request__opened_without_description.json
new file mode 100644
index 0000000000..255d000fe9
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_pull_request__opened_without_description.json
@@ -0,0 +1,129 @@
+{
+ "subscriptionId": "a0e7f493-8c02-498d-b9ee-86a8612bc74e",
+ "notificationId": 2,
+ "id": "3559461b-0001-422f-8c1c-7946284839ab",
+ "eventType": "git.pullrequest.created",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki created pull request 2 (Raised 2nd PR!) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/",
+ "html": "Yuro Itaki created pull request 2 (Raised 2nd PR!) in test-zulip",
+ "markdown": "Yuro Itaki created [pull request 2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2) (Raised 2nd PR!) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki created pull request 2 (Raised 2nd PR!) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/\r\nRaised 2nd PR!\r\n",
+ "html": "Yuro Itaki created pull request 2 (Raised 2nd PR!) in test-zulipRaised 2nd PR!
",
+ "markdown": "Yuro Itaki created [pull request 2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2) (Raised 2nd PR!) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)\r\nRaised 2nd PR!\r\n"
+ },
+ "resource": {
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "revision": 11,
+ "visibility": "private",
+ "lastUpdateTime": "2022-07-17T05:01:05.987Z"
+ },
+ "size": 4425,
+ "remoteUrl": "https://ttchong@dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "sshUrl": "git@ssh.dev.azure.com:v3/ttchong/test-zulip/test-zulip",
+ "webUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "isDisabled": false
+ },
+ "pullRequestId": 2,
+ "codeReviewId": 2,
+ "status": "active",
+ "createdBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "creationDate": "2022-07-24T08:45:53.9577409Z",
+ "title": "Raised 2nd PR!",
+ "sourceRefName": "refs/heads/stg",
+ "targetRefName": "refs/heads/main",
+ "mergeStatus": "succeeded",
+ "isDraft": false,
+ "mergeId": "675bced4-738a-4f9e-b255-a30dce8b91df",
+ "lastMergeSourceCommit": {
+ "commitId": "fe3b16dd2079fc96f4e31752b0e3e0782389b469",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/fe3b16dd2079fc96f4e31752b0e3e0782389b469"
+ },
+ "lastMergeTargetCommit": {
+ "commitId": "0929a3404b39f6e39076a640779b2c1c961e19b5",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/0929a3404b39f6e39076a640779b2c1c961e19b5"
+ },
+ "lastMergeCommit": {
+ "commitId": "037561654dedfcc22e96230eee4550916357ec7a",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-24T08:45:54Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-24T08:45:54Z"
+ },
+ "comment": "Merge pull request 2 from stg into main",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/037561654dedfcc22e96230eee4550916357ec7a"
+ },
+ "reviewers": [
+ {
+ "reviewerUrl": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/2/reviewers/ed580ebe-b4a9-485d-bfed-c58ba519d7b6",
+ "vote": 0,
+ "hasDeclined": false,
+ "isFlagged": false,
+ "displayName": "Project Collection Build Service (Pad9e1dcf-6055-4fc7-a146-5511ab5ab1e8)",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/ed580ebe-b4a9-485d-bfed-c58ba519d7b6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/svc.YTU2Y2JhNGEtZGM4MC00ZGQyLTkxZmEtNmZlNzA0N2ZlYTdjOkJ1aWxkOmFkOWUxZGNmLTYwNTUtNGZjNy1hMTQ2LTU1MTFhYjVhYjFlOA"
+ }
+ },
+ "id": "ed580ebe-b4a9-485d-bfed-c58ba519d7b6",
+ "uniqueName": "Build\\ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=ed580ebe-b4a9-485d-bfed-c58ba519d7b6"
+ }
+ ],
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/2",
+ "_links": {
+ "web": {
+ "href": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2"
+ },
+ "statuses": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/2/statuses"
+ }
+ },
+ "supportsIterations": true,
+ "artifactId": "vstfs:///Git/PullRequestId/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2%2f98f0ce59-a912-43d5-96d2-bc0942a03f7b%2f2"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-24T08:46:00.8535001Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_pull_request__updated.json b/zerver/webhooks/azuredevops/fixtures/code_pull_request__updated.json
new file mode 100644
index 0000000000..97d0a6ba07
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_pull_request__updated.json
@@ -0,0 +1,136 @@
+{
+ "subscriptionId": "d21a27a6-3b89-45fb-9af9-0fe220348c1d",
+ "notificationId": 1,
+ "id": "6544f94b-ce5d-4bc1-b4be-30559b589d15",
+ "eventType": "git.pullrequest.updated",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki updated the source branch of pull request 2 (Raised 2nd PR!) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/",
+ "html": "Yuro Itaki updated the source branch of pull request 2 (Raised 2nd PR!) in test-zulip",
+ "markdown": "Yuro Itaki updated the source branch of [pull request 2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2) (Raised 2nd PR!) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki updated the source branch of pull request 2 (Raised 2nd PR!) in test-zulip\r\nhttps://dev.azure.com/ttchong/test-zulip/_git/test-zulip/\r\nRaised 2nd PR!\r\n",
+ "html": "Yuro Itaki updated the source branch of pull request 2 (Raised 2nd PR!) in test-zulipRaised 2nd PR!
",
+ "markdown": "Yuro Itaki updated the source branch of [pull request 2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2) (Raised 2nd PR!) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)\r\nRaised 2nd PR!\r\n"
+ },
+ "resource": {
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "revision": 11,
+ "visibility": "private",
+ "lastUpdateTime": "2022-07-17T05:01:05.987Z"
+ },
+ "size": 7840,
+ "remoteUrl": "https://ttchong@dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "sshUrl": "git@ssh.dev.azure.com:v3/ttchong/test-zulip/test-zulip",
+ "webUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip",
+ "isDisabled": false
+ },
+ "pullRequestId": 2,
+ "codeReviewId": 2,
+ "status": "active",
+ "createdBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "creationDate": "2022-07-24T08:45:53.9577409Z",
+ "title": "Raised 2nd PR!",
+ "description": "Raised 2nd PR!",
+ "sourceRefName": "refs/heads/stg",
+ "targetRefName": "refs/heads/main",
+ "mergeStatus": "succeeded",
+ "isDraft": false,
+ "mergeId": "675bced4-738a-4f9e-b255-a30dce8b91df",
+ "lastMergeSourceCommit": {
+ "commitId": "aefa35fb7c4505b9b8337aff3c4be96eddbbd861",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/aefa35fb7c4505b9b8337aff3c4be96eddbbd861"
+ },
+ "lastMergeTargetCommit": {
+ "commitId": "b2606c193460441311f4d31f4729589c77f5efe0",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b2606c193460441311f4d31f4729589c77f5efe0"
+ },
+ "lastMergeCommit": {
+ "commitId": "f0fb095f4e89065e2edccaedae4535979e212b78",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-30T08:42:00Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-30T08:42:00Z"
+ },
+ "comment": "Merge pull request 2 from stg into main",
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/f0fb095f4e89065e2edccaedae4535979e212b78"
+ },
+ "reviewers": [
+ {
+ "reviewerUrl": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/2/reviewers/ed580ebe-b4a9-485d-bfed-c58ba519d7b6",
+ "vote": 0,
+ "hasDeclined": false,
+ "isFlagged": false,
+ "displayName": "Project Collection Build Service (Pad9e1dcf-6055-4fc7-a146-5511ab5ab1e8)",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/ed580ebe-b4a9-485d-bfed-c58ba519d7b6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/svc.YTU2Y2JhNGEtZGM4MC00ZGQyLTkxZmEtNmZlNzA0N2ZlYTdjOkJ1aWxkOmFkOWUxZGNmLTYwNTUtNGZjNy1hMTQ2LTU1MTFhYjVhYjFlOA"
+ }
+ },
+ "id": "ed580ebe-b4a9-485d-bfed-c58ba519d7b6",
+ "uniqueName": "Build\\ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=ed580ebe-b4a9-485d-bfed-c58ba519d7b6"
+ }
+ ],
+ "url": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/2",
+ "_links": {
+ "web": {
+ "href": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2"
+ },
+ "statuses": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pullRequests/2/statuses"
+ }
+ },
+ "completionOptions": {
+ "mergeCommitMessage": "Merged PR 2: Raised 2nd PR!\n\nRaised 2nd PR!",
+ "mergeStrategy": "noFastForward",
+ "transitionWorkItems": true,
+ "autoCompleteIgnoreConfigIds": []
+ },
+ "supportsIterations": true,
+ "artifactId": "vstfs:///Git/PullRequestId/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2%2f98f0ce59-a912-43d5-96d2-bc0942a03f7b%2f2"
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-30T08:42:07.2382228Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_push.json b/zerver/webhooks/azuredevops/fixtures/code_push.json
new file mode 100644
index 0000000000..5ddc5cb3cd
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_push.json
@@ -0,0 +1,107 @@
+{
+ "subscriptionId": "bb3738ff-00ae-472e-b752-d192c676780c",
+ "notificationId": 2,
+ "id": "9c335e3e-e047-405c-a420-d679bd54a299",
+ "eventType": "git.push",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki pushed updates to test-zulip:main\r\n(https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)",
+ "html": "Yuro Itaki pushed updates to test-zulip:main",
+ "markdown": "Yuro Itaki pushed updates to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki pushed a commit to test-zulip:main\r\n - Modify readme b0ce2f20 (https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369)",
+ "html": "Yuro Itaki pushed a commit to test-zulip:main\r\n",
+ "markdown": "Yuro Itaki pushed a commit to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)\r\n* Modify readme [b0ce2f20](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369)"
+ },
+ "resource": {
+ "commits": [
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ }
+ ],
+ "refUpdates": [
+ {
+ "name": "refs/heads/main",
+ "oldObjectId": "51515957669f93c543df09f8f3e7f47c3613c879",
+ "newObjectId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ }
+ ],
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "visibility": "unchanged",
+ "lastUpdateTime": "0001-01-01T00:00:00"
+ },
+ "defaultBranch": "refs/heads/main",
+ "remoteUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip"
+ },
+ "pushedBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "pushId": 2,
+ "date": "2022-07-17T07:14:05.9409049Z",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/2",
+ "_links": {
+ "self": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/2"
+ },
+ "repository": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b"
+ },
+ "commits": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/2/commits"
+ },
+ "pusher": {
+ "href": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6"
+ },
+ "refs": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/refs/heads/main"
+ }
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-17T07:14:12.5477052Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_push__commits_more_than_limit.json b/zerver/webhooks/azuredevops/fixtures/code_push__commits_more_than_limit.json
new file mode 100644
index 0000000000..3ec900b6dd
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_push__commits_more_than_limit.json
@@ -0,0 +1,842 @@
+{
+ "subscriptionId": "bb3738ff-00ae-472e-b752-d192c676780c",
+ "notificationId": 2,
+ "id": "9c335e3e-e047-405c-a420-d679bd54a299",
+ "eventType": "git.push",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki pushed updates to test-zulip:main\r\n(https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)",
+ "html": "Yuro Itaki pushed updates to test-zulip:main",
+ "markdown": "Yuro Itaki pushed updates to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki pushed 50 commits to test-zulip:main\r\n - Modify readme b0ce2f20 (https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369)",
+ "html": "Yuro Itaki pushed 50 commits to test-zulip:main\r\n",
+ "markdown": "Yuro Itaki pushed 50 commits to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)\r\n* Modify readme [b0ce2f20](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369)"
+ },
+ "resource": {
+ "commits": [
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ },
+ {
+ "commitId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T07:14:01Z"
+ },
+ "comment": "Modify readme",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ }
+ ],
+ "refUpdates": [
+ {
+ "name": "refs/heads/main",
+ "oldObjectId": "51515957669f93c543df09f8f3e7f47c3613c879",
+ "newObjectId": "b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369"
+ }
+ ],
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "visibility": "unchanged",
+ "lastUpdateTime": "0001-01-01T00:00:00"
+ },
+ "defaultBranch": "refs/heads/main",
+ "remoteUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip"
+ },
+ "pushedBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "pushId": 2,
+ "date": "2022-07-17T07:14:05.9409049Z",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/2",
+ "_links": {
+ "self": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/2"
+ },
+ "repository": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b"
+ },
+ "commits": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/2/commits"
+ },
+ "pusher": {
+ "href": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6"
+ },
+ "refs": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/refs/heads/main"
+ }
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-17T07:14:12.5477052Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_push__local_branch_without_commits.json b/zerver/webhooks/azuredevops/fixtures/code_push__local_branch_without_commits.json
new file mode 100644
index 0000000000..0565924e76
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_push__local_branch_without_commits.json
@@ -0,0 +1,90 @@
+{
+ "subscriptionId": "eead0877-fb06-46e2-9ec1-5f852a6d0297",
+ "notificationId": 3,
+ "id": "7687ae00-23fe-44e8-a66b-bb61d279e2e1",
+ "eventType": "git.push",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki pushed updates to test-zulip:dev\r\n(https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBdev)",
+ "html": "Yuro Itaki pushed updates to test-zulip:dev",
+ "markdown": "Yuro Itaki pushed updates to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[dev](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBdev)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki pushed a commit to test-zulip:dev\r\n - Add reply 0929a340 (https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5)",
+ "html": "Yuro Itaki pushed a commit to test-zulip:dev\r\n",
+ "markdown": "Yuro Itaki pushed a commit to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[dev](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBdev)\r\n* Add reply [0929a340](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5)"
+ },
+ "resource": {
+ "refUpdates": [
+ {
+ "name": "refs/heads/dev",
+ "oldObjectId": "0000000000000000000000000000000000000000",
+ "newObjectId": "0929a3404b39f6e39076a640779b2c1c961e19b5"
+ }
+ ],
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "visibility": "unchanged",
+ "lastUpdateTime": "0001-01-01T00:00:00"
+ },
+ "defaultBranch": "refs/heads/main",
+ "remoteUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip"
+ },
+ "pushedBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "pushId": 5,
+ "date": "2022-07-17T10:18:15.3667447Z",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/5",
+ "_links": {
+ "self": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/5"
+ },
+ "repository": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b"
+ },
+ "commits": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/5/commits"
+ },
+ "pusher": {
+ "href": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6"
+ },
+ "refs": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/refs/heads/dev"
+ }
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-17T10:18:22.69149Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_push__multiple_committers.json b/zerver/webhooks/azuredevops/fixtures/code_push__multiple_committers.json
new file mode 100644
index 0000000000..189499ca75
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_push__multiple_committers.json
@@ -0,0 +1,122 @@
+{
+ "subscriptionId": "eead0877-fb06-46e2-9ec1-5f852a6d0297",
+ "notificationId": 2,
+ "id": "7afcf4e6-f3d0-47f0-ba5f-395f0776496b",
+ "eventType": "git.push",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki pushed updates to test-zulip:main\r\n(https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)",
+ "html": "Yuro Itaki pushed updates to test-zulip:main",
+ "markdown": "Yuro Itaki pushed updates to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki pushed 2 commits to test-zulip:main\r\n - Add reply 0929a340 (https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5)\r\n - Add how are you 819ce8de (https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/819ce8de51bedfc250c202edcaee0ce8dc70bf3b)",
+ "html": "Yuro Itaki pushed 2 commits to test-zulip:main\r\n",
+ "markdown": "Yuro Itaki pushed 2 commits to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)\r\n* Add reply [0929a340](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5)\r\n* Add how are you [819ce8de](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/819ce8de51bedfc250c202edcaee0ce8dc70bf3b)"
+ },
+ "resource": {
+ "commits": [
+ {
+ "commitId": "0929a3404b39f6e39076a640779b2c1c961e19b5",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T09:13:02Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T09:13:02Z"
+ },
+ "comment": "Add reply",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/0929a3404b39f6e39076a640779b2c1c961e19b5"
+ },
+ {
+ "commitId": "819ce8de51bedfc250c202edcaee0ce8dc70bf3b",
+ "author": {
+ "name": "Itachi Sensei",
+ "email": "itachisensei@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "committer": {
+ "name": "Itachi Sensei",
+ "email": "itachisensei@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "comment": "Add how are you",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/819ce8de51bedfc250c202edcaee0ce8dc70bf3b"
+ }
+ ],
+ "refUpdates": [
+ {
+ "name": "refs/heads/main",
+ "oldObjectId": "cc21b940719cc372b364d932eb39e528b0ec2a91",
+ "newObjectId": "0929a3404b39f6e39076a640779b2c1c961e19b5"
+ }
+ ],
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "visibility": "unchanged",
+ "lastUpdateTime": "0001-01-01T00:00:00"
+ },
+ "defaultBranch": "refs/heads/main",
+ "remoteUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip"
+ },
+ "pushedBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "pushId": 4,
+ "date": "2022-07-17T09:13:10.0775053Z",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/4",
+ "_links": {
+ "self": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/4"
+ },
+ "repository": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b"
+ },
+ "commits": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/4/commits"
+ },
+ "pusher": {
+ "href": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6"
+ },
+ "refs": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/refs/heads/main"
+ }
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-17T09:13:17.0920723Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_push__multiple_committers_with_others.json b/zerver/webhooks/azuredevops/fixtures/code_push__multiple_committers_with_others.json
new file mode 100644
index 0000000000..41db88be87
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_push__multiple_committers_with_others.json
@@ -0,0 +1,182 @@
+{
+ "subscriptionId": "eead0877-fb06-46e2-9ec1-5f852a6d0297",
+ "notificationId": 2,
+ "id": "7afcf4e6-f3d0-47f0-ba5f-395f0776496b",
+ "eventType": "git.push",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki pushed updates to test-zulip:main\r\n(https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)",
+ "html": "Yuro Itaki pushed updates to test-zulip:main",
+ "markdown": "Yuro Itaki pushed updates to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki pushed 6 commits to test-zulip:main\r\n - Add reply 0929a340 (https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5)\r\n - Add how are you 819ce8de (https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/819ce8de51bedfc250c202edcaee0ce8dc70bf3b)",
+ "html": "Yuro Itaki pushed 6 commits to test-zulip:main\r\n",
+ "markdown": "Yuro Itaki pushed 6 commits to [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[main](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBmain)\r\n* Add reply [0929a340](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5)\r\n* Add how are you [819ce8de](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/819ce8de51bedfc250c202edcaee0ce8dc70bf3b)"
+ },
+ "resource": {
+ "commits": [
+ {
+ "commitId": "0929a3404b39f6e39076a640779b2c1c961e19b5",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T09:13:02Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T09:13:02Z"
+ },
+ "comment": "Add reply",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/0929a3404b39f6e39076a640779b2c1c961e19b5"
+ },
+ {
+ "commitId": "819ce8de51bedfc250c202edcaee0ce8dc70bf3b",
+ "author": {
+ "name": "Itachi Sensei",
+ "email": "itachisensei@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "committer": {
+ "name": "Itachi Sensei",
+ "email": "itachisensei@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "comment": "Add how are you",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/819ce8de51bedfc250c202edcaee0ce8dc70bf3b"
+ },
+ {
+ "commitId": "819ce8de51bedfc250c202edcaee0ce8dc70bf3b",
+ "author": {
+ "name": "Itachi Sensei",
+ "email": "itachisensei@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "committer": {
+ "name": "Itachi Sensei",
+ "email": "itachisensei@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "comment": "Add how are you",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/819ce8de51bedfc250c202edcaee0ce8dc70bf3b"
+ },
+ {
+ "commitId": "819ce8de51bedfc250c202edcaee0ce8dc70bf3b",
+ "author": {
+ "name": "Lelouch Strange",
+ "email": "lelouchstrange@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "committer": {
+ "name": "Lelouch Strange",
+ "email": "lelouchstrange@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "comment": "Add how are you",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/819ce8de51bedfc250c202edcaee0ce8dc70bf3b"
+ },
+ {
+ "commitId": "819ce8de51bedfc250c202edcaee0ce8dc70bf3b",
+ "author": {
+ "name": "Jonas Nielsen",
+ "email": "jonasnielsen@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "committer": {
+ "name": "Jonas Nielsen",
+ "email": "jonasnielsen@email.com",
+ "date": "2022-07-17T09:12:35Z"
+ },
+ "comment": "Add how are you",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/819ce8de51bedfc250c202edcaee0ce8dc70bf3b"
+ },
+ {
+ "commitId": "0929a3404b39f6e39076a640779b2c1c961e19b5",
+ "author": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T09:13:02Z"
+ },
+ "committer": {
+ "name": "Yuro Itaki",
+ "email": "yuroitaki@email.com",
+ "date": "2022-07-17T09:13:02Z"
+ },
+ "comment": "Add reply",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/commits/0929a3404b39f6e39076a640779b2c1c961e19b5"
+ }
+ ],
+ "refUpdates": [
+ {
+ "name": "refs/heads/main",
+ "oldObjectId": "cc21b940719cc372b364d932eb39e528b0ec2a91",
+ "newObjectId": "0929a3404b39f6e39076a640779b2c1c961e19b5"
+ }
+ ],
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "visibility": "unchanged",
+ "lastUpdateTime": "0001-01-01T00:00:00"
+ },
+ "defaultBranch": "refs/heads/main",
+ "remoteUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip"
+ },
+ "pushedBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "pushId": 4,
+ "date": "2022-07-17T09:13:10.0775053Z",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/4",
+ "_links": {
+ "self": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/4"
+ },
+ "repository": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b"
+ },
+ "commits": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/4/commits"
+ },
+ "pusher": {
+ "href": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6"
+ },
+ "refs": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/refs/heads/main"
+ }
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-17T09:13:17.0920723Z"
+}
diff --git a/zerver/webhooks/azuredevops/fixtures/code_push__remove_branch.json b/zerver/webhooks/azuredevops/fixtures/code_push__remove_branch.json
new file mode 100644
index 0000000000..6c155fbe50
--- /dev/null
+++ b/zerver/webhooks/azuredevops/fixtures/code_push__remove_branch.json
@@ -0,0 +1,90 @@
+{
+ "subscriptionId": "eead0877-fb06-46e2-9ec1-5f852a6d0297",
+ "notificationId": 4,
+ "id": "91264257-1f51-46d3-bba3-5448d40a724a",
+ "eventType": "git.push",
+ "publisherId": "tfs",
+ "message": {
+ "text": "Yuro Itaki deleted test-zulip:dev\r\n(https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBdev)",
+ "html": "Yuro Itaki deleted test-zulip:dev",
+ "markdown": "Yuro Itaki deleted [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):dev"
+ },
+ "detailedMessage": {
+ "text": "Yuro Itaki deleted test-zulip:dev",
+ "html": "Yuro Itaki deleted test-zulip:dev",
+ "markdown": "Yuro Itaki deleted [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/):[dev](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/#version=GBdev)"
+ },
+ "resource": {
+ "refUpdates": [
+ {
+ "name": "refs/heads/dev",
+ "oldObjectId": "0929a3404b39f6e39076a640779b2c1c961e19b5",
+ "newObjectId": "0000000000000000000000000000000000000000"
+ }
+ ],
+ "repository": {
+ "id": "98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b",
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "name": "test-zulip",
+ "url": "https://dev.azure.com/ttchong/_apis/projects/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "state": "wellFormed",
+ "visibility": "unchanged",
+ "lastUpdateTime": "0001-01-01T00:00:00"
+ },
+ "defaultBranch": "refs/heads/main",
+ "remoteUrl": "https://dev.azure.com/ttchong/test-zulip/_git/test-zulip"
+ },
+ "pushedBy": {
+ "displayName": "Yuro Itaki",
+ "url": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6",
+ "_links": {
+ "avatar": {
+ "href": "https://dev.azure.com/ttchong/_apis/GraphProfile/MemberAvatars/msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ }
+ },
+ "id": "107e08c7-2725-675e-a1b0-281729035ea6",
+ "uniqueName": "yuroitaki@email.com",
+ "imageUrl": "https://dev.azure.com/ttchong/_api/_common/identityImage?id=107e08c7-2725-675e-a1b0-281729035ea6",
+ "descriptor": "msa.MTA3ZTA4YzctMjcyNS03NzVlLWExYjAtMjgxNzI5MDM1ZWE2"
+ },
+ "pushId": 6,
+ "date": "2022-07-17T11:08:58.0728467Z",
+ "url": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/6",
+ "_links": {
+ "self": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/6"
+ },
+ "repository": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b"
+ },
+ "commits": {
+ "href": "https://dev.azure.com/ttchong/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/pushes/6/commits"
+ },
+ "pusher": {
+ "href": "https://spsprodsea2.vssps.visualstudio.com/Aa56cba4a-dc80-4dd2-91fa-6fe7047fea7c/_apis/Identities/107e08c7-2725-675e-a1b0-281729035ea6"
+ },
+ "refs": {
+ "href": "https://dev.azure.com/ttchong/068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2/_apis/git/repositories/98f0ce59-a912-43d5-96d2-bc0942a03f7b/refs/heads/dev"
+ }
+ }
+ },
+ "resourceVersion": "1.0",
+ "resourceContainers": {
+ "collection": {
+ "id": "ad9e1dcf-6055-4fc7-a146-5511ab5ab1e8",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "account": {
+ "id": "a56cba4a-dc80-4dd2-91fa-6fe7047fea7c",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ },
+ "project": {
+ "id": "068d2409-14eb-4d8a-88bf-c8a9e7f5b4e2",
+ "baseUrl": "https://dev.azure.com/ttchong/"
+ }
+ },
+ "createdDate": "2022-07-17T11:09:05.1538362Z"
+}
diff --git a/zerver/webhooks/azuredevops/tests.py b/zerver/webhooks/azuredevops/tests.py
new file mode 100644
index 0000000000..2f532d47cf
--- /dev/null
+++ b/zerver/webhooks/azuredevops/tests.py
@@ -0,0 +1,94 @@
+from unittest.mock import MagicMock, patch
+
+from zerver.lib.test_classes import WebhookTestCase
+from zerver.lib.webhooks.git import COMMITS_LIMIT
+
+
+class AzuredevopsHookTests(WebhookTestCase):
+ STREAM_NAME = "azure-devops"
+ URL_TEMPLATE = "/api/v1/external/azuredevops?&api_key={api_key}&stream={stream}"
+ WEBHOOK_DIR_NAME = "azuredevops"
+
+ def test_push_event_message(self) -> None:
+ expected_topic = "test-zulip / main"
+ expected_message = "Yuro Itaki [pushed](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/branchCompare?baseVersion=GC51515957669f93c543df09f8f3e7f47c3613c879&targetVersion=GCb0ce2f2009c3c87dbefadf61d7eb2c0697a6f369&_a=files) 1 commit to branch main.\n\n* Modify readme ([b0ce2f2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369))"
+ self.check_webhook("code_push", expected_topic, expected_message)
+
+ def test_push_event_message_filtered_by_branches(self) -> None:
+ self.url = self.build_webhook_url(branches="main,dev")
+ expected_topic = "test-zulip / main"
+ expected_message = "Yuro Itaki [pushed](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/branchCompare?baseVersion=GC51515957669f93c543df09f8f3e7f47c3613c879&targetVersion=GCb0ce2f2009c3c87dbefadf61d7eb2c0697a6f369&_a=files) 1 commit to branch main.\n\n* Modify readme ([b0ce2f2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369))"
+ self.check_webhook("code_push", expected_topic, expected_message)
+
+ @patch("zerver.lib.webhooks.common.check_send_webhook_message")
+ def test_push_event_message_filtered_by_branches_ignore(
+ self, check_send_webhook_message_mock: MagicMock
+ ) -> None:
+ self.url = self.build_webhook_url(branches="development")
+ payload = self.get_body("code_push")
+ result = self.client_post(self.url, payload, content_type="application/json")
+ self.assertFalse(check_send_webhook_message_mock.called)
+ self.assert_json_success(result)
+
+ def test_push_local_branch_without_commits(self) -> None:
+ expected_topic = "test-zulip / dev"
+ expected_message = "Yuro Itaki [pushed](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/branchCompare?baseVersion=GC0000000000000000000000000000000000000000&targetVersion=GC0929a3404b39f6e39076a640779b2c1c961e19b5&_a=files) the branch dev."
+ self.check_webhook(
+ "code_push__local_branch_without_commits", expected_topic, expected_message
+ )
+
+ def test_push_multiple_committers(self) -> None:
+ expected_topic = "test-zulip / main"
+ expected_message = "Yuro Itaki [pushed](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/branchCompare?baseVersion=GCcc21b940719cc372b364d932eb39e528b0ec2a91&targetVersion=GC0929a3404b39f6e39076a640779b2c1c961e19b5&_a=files) 2 commits to branch main. Commits by Itachi Sensei (1) and Yuro Itaki (1).\n\n* Add reply ([0929a34](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5))\n* Add how are you ([819ce8d](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/819ce8de51bedfc250c202edcaee0ce8dc70bf3b))"
+ self.check_webhook("code_push__multiple_committers", expected_topic, expected_message)
+
+ def test_push_multiple_committers_with_others(self) -> None:
+ expected_topic = "test-zulip / main"
+ commits_info = "* Add how are you ([819ce8d](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/819ce8de51bedfc250c202edcaee0ce8dc70bf3b))\n"
+ expected_message = f"Yuro Itaki [pushed](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/branchCompare?baseVersion=GCcc21b940719cc372b364d932eb39e528b0ec2a91&targetVersion=GC0929a3404b39f6e39076a640779b2c1c961e19b5&_a=files) 6 commits to branch main. Commits by Itachi Sensei (2), Yuro Itaki (2), Jonas Nielsen (1) and others (1).\n\n* Add reply ([0929a34](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5))\n{commits_info * 4}* Add reply ([0929a34](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/0929a3404b39f6e39076a640779b2c1c961e19b5))"
+ self.check_webhook(
+ "code_push__multiple_committers_with_others", expected_topic, expected_message
+ )
+
+ def test_push_commits_more_than_limit(self) -> None:
+ expected_topic = "test-zulip / main"
+ commits_info = "* Modify readme ([b0ce2f2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/commit/b0ce2f2009c3c87dbefadf61d7eb2c0697a6f369))\n"
+ expected_message = f"Yuro Itaki [pushed](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/branchCompare?baseVersion=GC51515957669f93c543df09f8f3e7f47c3613c879&targetVersion=GCb0ce2f2009c3c87dbefadf61d7eb2c0697a6f369&_a=files) 50 commits to branch main.\n\n{commits_info * COMMITS_LIMIT}[and {50 - COMMITS_LIMIT} more commit(s)]"
+ self.check_webhook("code_push__commits_more_than_limit", expected_topic, expected_message)
+
+ def test_push_remove_branch(self) -> None:
+ expected_topic = "test-zulip / dev"
+ expected_message = "Yuro Itaki [pushed](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/branchCompare?baseVersion=GC0929a3404b39f6e39076a640779b2c1c961e19b5&targetVersion=GC0000000000000000000000000000000000000000&_a=files) the branch dev."
+ self.check_webhook("code_push__remove_branch", expected_topic, expected_message)
+
+ def test_pull_request_opened(self) -> None:
+ expected_topic = "test-zulip / PR #1 Add PR request"
+ expected_message = "Yuro Itaki created [PR #1 Add PR request](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1) from `dev` to `main`:\n\n~~~ quote\nAdd PR request\n~~~"
+ self.check_webhook("code_pull_request__opened", expected_topic, expected_message)
+
+ def test_pull_request_opened_without_description(self) -> None:
+ expected_topic = "test-zulip / PR #2 Raised 2nd PR!"
+ expected_message = "Yuro Itaki created [PR #2 Raised 2nd PR!](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2) from `stg` to `main`."
+ self.check_webhook(
+ "code_pull_request__opened_without_description", expected_topic, expected_message
+ )
+
+ def test_pull_request_merged(self) -> None:
+ expected_topic = "test-zulip / PR #1 Add PR request"
+ expected_message = "Yuro Itaki merged [PR #1 Add PR request](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/1)."
+ self.check_webhook("code_pull_request__merged", expected_topic, expected_message)
+
+ @patch("zerver.lib.webhooks.common.check_send_webhook_message")
+ def test_pull_request_merge_attempted_ignore(
+ self, check_send_webhook_message_mock: MagicMock
+ ) -> None:
+ self.url = self.build_webhook_url()
+ payload = self.get_body("code_pull_request__merge_attempted")
+ result = self.client_post(self.url, payload, content_type="application/json")
+ self.assertFalse(check_send_webhook_message_mock.called)
+ self.assert_json_success(result)
+
+ def test_pull_request_updated(self) -> None:
+ expected_topic = "test-zulip / PR #2 Raised 2nd PR!"
+ expected_message = "Yuro Itaki updated [PR #2 Raised 2nd PR!](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2)\n\n~~~ quote\nYuro Itaki updated the source branch of [pull request 2](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/pullrequest/2) (Raised 2nd PR!) in [test-zulip](https://dev.azure.com/ttchong/test-zulip/_git/test-zulip/)\r\nRaised 2nd PR!\r\n\n~~~"
+ self.check_webhook("code_pull_request__updated", expected_topic, expected_message)
diff --git a/zerver/webhooks/azuredevops/view.py b/zerver/webhooks/azuredevops/view.py
new file mode 100644
index 0000000000..fd708331ad
--- /dev/null
+++ b/zerver/webhooks/azuredevops/view.py
@@ -0,0 +1,182 @@
+from typing import Callable, Dict, Optional
+
+from django.http import HttpRequest, HttpResponse
+
+from zerver.decorator import webhook_view
+from zerver.lib.exceptions import UnsupportedWebhookEventType
+from zerver.lib.request import REQ, has_request_variables
+from zerver.lib.response import json_success
+from zerver.lib.validator import WildValue, check_int, check_string, to_wild_value
+from zerver.lib.webhooks.common import check_send_webhook_message
+from zerver.lib.webhooks.git import (
+ TOPIC_WITH_BRANCH_TEMPLATE,
+ TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE,
+ get_pull_request_event_message,
+ get_push_commits_event_message,
+)
+from zerver.models import UserProfile
+
+
+def get_code_pull_request_updated_body(payload: WildValue) -> str:
+ return get_pull_request_event_message(
+ get_code_pull_request_user_name(payload),
+ "updated",
+ get_code_pull_request_url(payload),
+ get_code_pull_request_id(payload),
+ message=payload["detailedMessage"]["markdown"].tame(check_string),
+ title=get_code_pull_request_title(payload),
+ )
+
+
+def get_code_pull_request_merged_body(payload: WildValue) -> str:
+ return get_pull_request_event_message(
+ get_code_pull_request_user_name(payload),
+ "merged",
+ get_code_pull_request_url(payload),
+ get_code_pull_request_id(payload),
+ title=get_code_pull_request_title(payload),
+ )
+
+
+def get_code_pull_request_opened_body(payload: WildValue) -> str:
+ if payload["resource"].get("description"):
+ description = payload["resource"]["description"].tame(check_string)
+ else:
+ description = None
+ return get_pull_request_event_message(
+ get_code_pull_request_user_name(payload),
+ "created",
+ get_code_pull_request_url(payload),
+ get_code_pull_request_id(payload),
+ payload["resource"]["sourceRefName"].tame(check_string).replace("refs/heads/", ""),
+ payload["resource"]["targetRefName"].tame(check_string).replace("refs/heads/", ""),
+ description,
+ title=get_code_pull_request_title(payload),
+ )
+
+
+def get_code_push_commits_body(payload: WildValue) -> str:
+ compare_url = "{}/branchCompare?baseVersion=GC{}&targetVersion=GC{}&_a=files".format(
+ get_code_repository_url(payload),
+ payload["resource"]["refUpdates"][0]["oldObjectId"].tame(check_string),
+ payload["resource"]["refUpdates"][0]["newObjectId"].tame(check_string),
+ )
+ commits_data = []
+ if payload["resource"].get("commits"):
+ for commit in payload["resource"]["commits"]:
+ commits_data.append(
+ {
+ "name": commit["author"]["name"].tame(check_string),
+ "sha": commit["commitId"].tame(check_string),
+ "url": "{}/commit/{}".format(
+ get_code_repository_url(payload), commit["commitId"].tame(check_string)
+ ),
+ "message": commit["comment"].tame(check_string),
+ }
+ )
+ return get_push_commits_event_message(
+ get_code_push_user_name(payload),
+ compare_url,
+ get_code_push_branch_name(payload),
+ commits_data,
+ )
+
+
+def get_code_push_user_name(payload: WildValue) -> str:
+ return payload["resource"]["pushedBy"]["displayName"].tame(check_string)
+
+
+def get_code_push_branch_name(payload: WildValue) -> str:
+ return (
+ payload["resource"]["refUpdates"][0]["name"].tame(check_string).replace("refs/heads/", "")
+ )
+
+
+def get_code_repository_name(payload: WildValue) -> str:
+ return payload["resource"]["repository"]["name"].tame(check_string)
+
+
+def get_code_repository_url(payload: WildValue) -> str:
+ return payload["resource"]["repository"]["remoteUrl"].tame(check_string)
+
+
+def get_code_pull_request_id(payload: WildValue) -> int:
+ return payload["resource"]["pullRequestId"].tame(check_int)
+
+
+def get_code_pull_request_title(payload: WildValue) -> str:
+ return payload["resource"]["title"].tame(check_string)
+
+
+def get_code_pull_request_url(payload: WildValue) -> str:
+ return payload["resource"]["_links"]["web"]["href"].tame(check_string)
+
+
+def get_code_pull_request_user_name(payload: WildValue) -> str:
+ return payload["resource"]["createdBy"]["displayName"].tame(check_string)
+
+
+def get_topic_based_on_event(payload: WildValue, event: str) -> str:
+ if event == "git.push":
+ return TOPIC_WITH_BRANCH_TEMPLATE.format(
+ repo=get_code_repository_name(payload), branch=get_code_push_branch_name(payload)
+ )
+ elif "pullrequest" in event:
+ return TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
+ repo=get_code_repository_name(payload),
+ type="PR",
+ id=get_code_pull_request_id(payload),
+ title=get_code_pull_request_title(payload),
+ )
+ return get_code_repository_name(payload) # nocoverage
+
+
+def get_event_name(payload: WildValue, branches: Optional[str]) -> Optional[str]:
+ event_name = payload["eventType"].tame(check_string)
+ if event_name == "git.push":
+ if branches is not None:
+ branch = get_code_push_branch_name(payload)
+ if branches.find(branch) == -1:
+ return None
+ if event_name == "git.pullrequest.merged":
+ status = payload["resource"]["status"].tame(check_string)
+ merge_status = payload["resource"]["mergeStatus"].tame(check_string)
+ # azure devops sends webhook messages when a merge is attempted, i.e. there is a merge conflict
+ # after a PR is created, or when there is no conflict when PR is updated
+ # we're only interested in the case when the PR is merged successfully
+ if status != "completed" or merge_status != "succeeded":
+ return None
+ if event_name in EVENT_FUNCTION_MAPPER:
+ return event_name
+ raise UnsupportedWebhookEventType(event_name)
+
+
+EVENT_FUNCTION_MAPPER: Dict[str, Callable[[WildValue], str]] = {
+ "git.push": get_code_push_commits_body,
+ "git.pullrequest.created": get_code_pull_request_opened_body,
+ "git.pullrequest.merged": get_code_pull_request_merged_body,
+ "git.pullrequest.updated": get_code_pull_request_updated_body,
+}
+
+ALL_EVENT_TYPES = list(EVENT_FUNCTION_MAPPER.keys())
+
+
+@webhook_view("AzureDevOps", all_event_types=ALL_EVENT_TYPES)
+@has_request_variables
+def api_azuredevops_webhook(
+ request: HttpRequest,
+ user_profile: UserProfile,
+ payload: WildValue = REQ(argument_type="body", converter=to_wild_value),
+ branches: Optional[str] = REQ(default=None),
+) -> HttpResponse:
+ event = get_event_name(payload, branches)
+ if event is None:
+ return json_success(request)
+
+ topic = get_topic_based_on_event(payload, event)
+
+ body_function = EVENT_FUNCTION_MAPPER[event]
+ body = body_function(payload)
+
+ check_send_webhook_message(request, user_profile, topic, body)
+ return json_success(request)