Repository: zulip/zulip

Issue:
github integration: show if push was forced
The github integration bot is really useful :heart:, I'd like to help make it even better.

The github integration currently does not inform the user if a push was forced or not, the message is the same for both cases.
We consider a force push to be an important security event and would like to be notified when one happens.

The current format in case of a force push is:
```
<username> [pushed](https://github.com/repo/...) <commits> to branch <branch_name>.

<changelist>
```
My proposed format is:
```
<username> :boom: [force pushed](https://github.com/repo/...) :boom: <commits> to branch <branch_name>.

<changelist>
```

I'm willing to do the implementation myself, let me know what you think :) .


Discussion:
I think probably the :boom: is unnecessary, but we'd be happy to merge a patch that did this!

https://zulip.com/api/incoming-webhooks-overview should help you find the code.


Hello @zulip/server-integrations members, this issue was labeled with the "area: integrations" label, so you may want to check it out!

<!-- areaLabelAddition -->

Thanks for responding @timabbott :)
Without the :boom: I believe it is easy to miss that the push was forced, especially when there are many push messages.
When a "bad" force push happens I think it is vital that all developers are aware of this as fast as possible and that actions to fix this are started as fast as possible.

For these reasons, it is necessary for the message to stand out.
We can play with it -- I wouldn't put 2 copies of :boom:, at least. It's possible a single copy at the start of the line would be appropriate. In any case, it'll be easier to play with when we have screenshots, which the development environment makes easy to generate. It seems possible that just the longer link text of "force pushed" might already stand out, which had been what I was thinking.

Resolving patch:
diff --git a/zerver/lib/webhooks/git.py b/zerver/lib/webhooks/git.py
--- a/zerver/lib/webhooks/git.py
+++ b/zerver/lib/webhooks/git.py
@@ -13,8 +13,8 @@
 COMMITS_MORE_THAN_LIMIT_TEMPLATE = "[and {commits_number} more commit(s)]"
 COMMIT_OR_COMMITS = "commit{}"
 
-PUSH_PUSHED_TEXT_WITH_URL = "[pushed]({compare_url}) {number_of_commits} {commit_or_commits}"
-PUSH_PUSHED_TEXT_WITHOUT_URL = "pushed {number_of_commits} {commit_or_commits}"
+PUSH_PUSHED_TEXT_WITH_URL = "[{push_type}]({compare_url}) {number_of_commits} {commit_or_commits}"
+PUSH_PUSHED_TEXT_WITHOUT_URL = "{push_type} {number_of_commits} {commit_or_commits}"
 
 PUSH_COMMITS_BASE = "{user_name} {pushed_text} to branch {branch_name}."
 PUSH_COMMITS_MESSAGE_TEMPLATE_WITH_COMMITTERS = (
@@ -35,10 +35,10 @@
     "{user_name} [deleted]({compare_url}) the branch {branch_name}."
 )
 PUSH_LOCAL_BRANCH_WITHOUT_COMMITS_MESSAGE_TEMPLATE = (
-    "{user_name} [pushed]({compare_url}) the branch {branch_name}."
+    "{user_name} [{push_type}]({compare_url}) the branch {branch_name}."
 )
 PUSH_LOCAL_BRANCH_WITHOUT_COMMITS_MESSAGE_WITHOUT_URL_TEMPLATE = (
-    "{user_name} pushed the branch {branch_name}."
+    "{user_name} {push_type} the branch {branch_name}."
 )
 PUSH_COMMITS_MESSAGE_EXTENSION = "Commits by {}"
 PUSH_COMMITTERS_LIMIT_INFO = 3
@@ -89,6 +89,7 @@ def get_push_commits_event_message(
     commits_data: List[Dict[str, Any]],
     is_truncated: bool = False,
     deleted: bool = False,
+    force_push: Optional[bool] = False,
 ) -> str:
     if not commits_data and deleted:
         return PUSH_DELETE_BRANCH_MESSAGE_TEMPLATE.format(
@@ -97,14 +98,17 @@ def get_push_commits_event_message(
             branch_name=branch_name,
         )
 
+    push_type = "force pushed" if force_push else "pushed"
     if not commits_data and not deleted:
         if compare_url:
             return PUSH_LOCAL_BRANCH_WITHOUT_COMMITS_MESSAGE_TEMPLATE.format(
+                push_type=push_type,
                 user_name=user_name,
                 compare_url=compare_url,
                 branch_name=branch_name,
             )
         return PUSH_LOCAL_BRANCH_WITHOUT_COMMITS_MESSAGE_WITHOUT_URL_TEMPLATE.format(
+            push_type=push_type,
             user_name=user_name,
             branch_name=branch_name,
         )
@@ -114,6 +118,7 @@ def get_push_commits_event_message(
     )
 
     pushed_text_message = pushed_message_template.format(
+        push_type=push_type,
         compare_url=compare_url,
         number_of_commits=len(commits_data),
         commit_or_commits=COMMIT_OR_COMMITS.format("s" if len(commits_data) > 1 else ""),
diff --git a/zerver/webhooks/github/view.py b/zerver/webhooks/github/view.py
--- a/zerver/webhooks/github/view.py
+++ b/zerver/webhooks/github/view.py
@@ -283,6 +283,7 @@ def get_push_commits_body(helper: Helper) -> str:
         get_branch_name_from_ref(payload["ref"].tame(check_string)),
         commits_data,
         deleted=payload["deleted"].tame(check_bool),
+        force_push=payload["forced"].tame(check_bool),
     )
 
 


Test patch:
diff --git a/zerver/webhooks/github/tests.py b/zerver/webhooks/github/tests.py
--- a/zerver/webhooks/github/tests.py
+++ b/zerver/webhooks/github/tests.py
@@ -37,6 +37,14 @@ def test_push_delete_branch(self) -> None:
         expected_message = "eeshangarg [deleted](https://github.com/eeshangarg/public-repo/compare/2e8cf535fb38...000000000000) the branch feature."
         self.check_webhook("push__delete_branch", "public-repo / feature", expected_message)
 
+    def test_push_force_1_commit(self) -> None:
+        expected_message = "sbansal1999 [force pushed](https://github.com/sbansal1999/zulip/compare/b6de8891fc10...971d76ca3094) 1 commit to branch temp.\n\n* log: Add important.txt which is useful for logging errors. ([971d76ca309](https://github.com/sbansal1999/zulip/commit/971d76ca309446a9c20381f6271cea8a59b4e40a))"
+        self.check_webhook("push__force_1_commit", "zulip / temp", expected_message)
+
+    def test_push__force_remove_commits(self) -> None:
+        expected_message = "sbansal1999 [force pushed](https://github.com/sbansal1999/zulip/compare/2084a91af9ca...9a8749ea8fe7) the branch temp."
+        self.check_webhook("push__force_remove_commits", "zulip / temp", expected_message)
+
     def test_push_local_branch_without_commits(self) -> None:
         expected_message = "eeshangarg [pushed](https://github.com/eeshangarg/public-repo/compare/feature) the branch feature."
         self.check_webhook(