internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: zulip/zulip

Issue:
Migrate away from `six` to pure Python 3 APIs
We still have a couple dozen uses of the [six](https://pythonhosted.org/six/) library in our codebase -- see `git grep six`. This library exists to help write code that works on both Python 2 and Python 3; now that we're on Python 3 only, we can make things cleaner by replacing each of these with using the Python 3 forms directly.

This is sort of a companion to #9203.



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

<!-- areaLabelNotification -->
Just a quick note: We have a few pieces of code under `tools/` like `tools/linter_lib` and `tools/lister.py` that we are likely to want to keep Python 2+3 compatible, since the plan includes extracting them to a separate project eventually.  
@zulipbot claim
Hello @badmon!

Thanks for your interest in Zulip! You have attempted to claim an issue without the labels "help wanted", "good first issue". Since you're a new contributor, you can only claim and submit pull requests for issues with the [help wanted](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee+label%3A%22help+wanted%22) or [good first issue](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee+label%3A%22good+first+issue%22) labels.

If this is your first time here, we recommend reading our [guide for new contributors](https://zulip.readthedocs.io/en/latest/overview/contributing.html) before getting started.

Resolving patch:
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -98,7 +98,6 @@
 
 from confirmation.models import Confirmation, create_confirmation_link
 from confirmation import settings as confirmation_settings
-from six import unichr
 
 from zerver.lib.bulk_create import bulk_create_users
 from zerver.lib.create_user import random_api_key
@@ -3915,7 +3914,7 @@ def decode_email_address(email: Text) -> Optional[Tuple[Text, Text]]:
         encoded_stream_name, token = msg_string.split('.')
     else:
         encoded_stream_name, token = msg_string.split('+')
-    stream_name = re.sub("%\d{4}", lambda x: unichr(int(x.group(0)[1:])), encoded_stream_name)
+    stream_name = re.sub("%\d{4}", lambda x: chr(int(x.group(0)[1:])), encoded_stream_name)
     return stream_name, token
 
 SubHelperT = Tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]]
diff --git a/zerver/views/email_log.py b/zerver/views/email_log.py
--- a/zerver/views/email_log.py
+++ b/zerver/views/email_log.py
@@ -12,7 +12,7 @@
     get_forward_address,
     set_forward_address,
 )
-from six.moves import urllib
+import urllib
 from confirmation.models import Confirmation, confirmation_url
 
 import os
diff --git a/zproject/email_backends.py b/zproject/email_backends.py
--- a/zproject/email_backends.py
+++ b/zproject/email_backends.py
@@ -1,7 +1,7 @@
 import logging
 
 from typing import List
-from six.moves import configparser
+import configparser
 
 import smtplib
 from email.mime.multipart import MIMEMultipart


Test patch:
Repository: zulip/zulip

Issue:
browser-support: .includes() not supported in internet explorer.
Replace occurrences of `.includes()` with `.indexOf() !== -1` wherever possible, it should cover most of the cases. There are 6 occurrences of includes in the code excluding the front-end tests, replacing the occurrences would be a better idea than adding a polyfill.

References:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes


Discussion:
@zulipbot claim

Resolving patch:
diff --git a/tools/linter_lib/custom_check.py b/tools/linter_lib/custom_check.py
--- a/tools/linter_lib/custom_check.py
+++ b/tools/linter_lib/custom_check.py
@@ -188,6 +188,9 @@ def build_custom_checkers(by_lang):
          'description': 'Do not concatenate i18n strings'},
         {'pattern': '\+.*i18n\.t\(.+\)',
          'description': 'Do not concatenate i18n strings'},
+        {'pattern': '[.]includes[(]',
+         'exclude': ['frontend_tests/'],
+         'description': '.includes() is incompatible with Internet Explorer. Use .indexOf() !== -1 instead.'},
         {'pattern': '[.]html[(]',
          'exclude_pattern': '[.]html[(]("|\'|templates|html|message.content|sub.rendered_description|i18n.t|rendered_|$|[)]|error_text|widget_elem|[$]error|[$][(]"<p>"[)])',
          'exclude': ['static/js/portico', 'static/js/lightbox.js', 'static/js/ui_report.js',


Test patch:
Repository: zulip/zulip

Issue:
Change "invite-only" wording to "private"
![screenshot from 2018-05-31 16-47-49](https://user-images.githubusercontent.com/549661/40807927-9f721af0-64f3-11e8-9a12-2de8aa2ee678.png)
We should be referring to private streams as "private streams" everywhere that isn't a code-level identifier. (see https://chat.zulip.org/#narrow/stream/19-documentation/subject/private.20versus.20invite-only/near/586071)


Discussion:
These can read:
**public stream**
**private stream** with **shared history**
**private stream** with **protected history**

for the three cases.
Hello @zulip/server-user-docs members, this issue was labeled with the "area: documentation (user)" label, so you may want to check it out!

<!-- areaLabelNotification -->
I'd love for this to happen before the 1.9.0 release, so our terminology is consistent in the released version.  @eeshangarg can you take this?

Resolving patch:
diff --git a/analytics/management/commands/stream_stats.py b/analytics/management/commands/stream_stats.py
--- a/analytics/management/commands/stream_stats.py
+++ b/analytics/management/commands/stream_stats.py
@@ -40,5 +40,5 @@ def handle(self, *args: Any, **options: str) -> None:
                                                                 active=True)),), end=' ')
                 num_messages = len(Message.objects.filter(recipient=recipient))
                 print("%12d" % (num_messages,))
-            print("%d invite-only streams" % (invite_only_count,))
+            print("%d private streams" % (invite_only_count,))
             print("")


Test patch:
diff --git a/docs/testing/manual-testing.md b/docs/testing/manual-testing.md
--- a/docs/testing/manual-testing.md
+++ b/docs/testing/manual-testing.md
@@ -287,7 +287,7 @@ First, we start off with "positive" tests.
         - Have Cordelia subscribe to the stream.
         - Verify Cordelia can see the previous message.
         - Have Cordelia post a message to the stream.
-    - Have Hamlet create an invite-only stream with Cordelia
+    - Have Hamlet create a private stream with Cordelia
       invited and test a two-way conversation between the two
       users.
 
diff --git a/frontend_tests/node_tests/templates.js b/frontend_tests/node_tests/templates.js
--- a/frontend_tests/node_tests/templates.js
+++ b/frontend_tests/node_tests/templates.js
@@ -1240,7 +1240,7 @@ run_test('subscription_settings', () => {
     html += render('subscription_settings', sub);
 
     var div = $(html).find(".subscription-type");
-    assert(div.text().indexOf('invite-only stream') > 0);
+    assert(div.text().indexOf('private stream') > 0);
 
     var anchor = $(html).find(".change-stream-privacy:first");
     assert.equal(anchor.text(), "[translated: Change]");
Repository: zulip/zulip

Issue:
Fix a bug in emoticon translation where newlines prevent translation.
Currently, there is a bug in emoticon translation where a newline before an emoticon prevents it from being translated. This only occurs server-side, as the message initially is translated correctly in the local echo, but then becomes incorrect after receiving the render from the server.

For instance,

 > :) test :) test

is translated into

 > 😃 test 😃 test

but

 > :) test
 > :) test

is incorrectly translated into

 > 😃 test 
 > :) test

due to the newline.

See [discussion](https://chat.zulip.org/#narrow/stream/9-issues/subject/display-setting.3A.20Translate.20emoticons/near/599540).



Discussion:

Resolving patch:
diff --git a/zerver/lib/bugdown/__init__.py b/zerver/lib/bugdown/__init__.py
--- a/zerver/lib/bugdown/__init__.py
+++ b/zerver/lib/bugdown/__init__.py
@@ -1560,7 +1560,7 @@ def extendMarkdown(self, md: markdown.Markdown, md_globals: Dict[str, Any]) -> N
             'tex',
             Tex(r'\B(?<!\$)\$\$(?P<body>[^\n_$](\\\$|[^$\n])*)\$\$(?!\$)\B'),
             '>backtick')
-        md.inlinePatterns.add('emoji', Emoji(EMOJI_REGEX), '_end')
+        md.inlinePatterns.add('emoji', Emoji(EMOJI_REGEX), '<nl')
         md.inlinePatterns.add('translate_emoticons', EmoticonTranslation(emoticon_regex), '>emoji')
         md.inlinePatterns.add('unicodeemoji', UnicodeEmoji(unicode_emoji_regex), '_end')
         md.inlinePatterns.add('link', AtomicLinkPattern(markdown.inlinepatterns.LINK_RE, md), '>avatar')


Test patch:
diff --git a/zerver/tests/fixtures/markdown_test_cases.json b/zerver/tests/fixtures/markdown_test_cases.json
--- a/zerver/tests/fixtures/markdown_test_cases.json
+++ b/zerver/tests/fixtures/markdown_test_cases.json
@@ -384,6 +384,13 @@
       "text_content": "a:) ;)b",
       "translate_emoticons": true
     },
+    {
+      "name": "translate_emoticons_newline",
+      "input":  ":) test\n:) test",
+      "expected_output": "<p><span class=\"emoji emoji-1f603\" title=\"smiley\">:smiley:</span> test<br>\n<span class=\"emoji emoji-1f603\" title=\"smiley\">:smiley:</span> test</p>",
+      "text_content": "\ud83d\ude03 test\n\ud83d\ude03 test",
+      "translate_emoticons": true
+    },
     {
       "name": "translate_emoticons_in_code",
       "input":  "`:)`",
Repository: zulip/zulip

Issue:
integrations: Match phrase instead of fuzzysearch
From https://chat.zulip.org/#narrow/stream/9-issues/topic/integrations.20search 


Discussion:

Resolving patch:
diff --git a/version.py b/version.py
--- a/version.py
+++ b/version.py
@@ -8,4 +8,4 @@
 # Typically, adding a dependency only requires a minor version bump, and
 # removing a dependency requires a major version bump.
 
-PROVISION_VERSION = '20.11'
+PROVISION_VERSION = '21.0'


Test patch:
diff --git a/frontend_tests/node_tests/common.js b/frontend_tests/node_tests/common.js
--- a/frontend_tests/node_tests/common.js
+++ b/frontend_tests/node_tests/common.js
@@ -6,3 +6,13 @@ run_test('basics', () => {
     common.autofocus('#home');
     assert($('#home').is_focused());
 });
+
+run_test('phrase_match', () => {
+    assert(common.phrase_match('tes', 'test'));
+    assert(common.phrase_match('Tes', 'test'));
+    assert(common.phrase_match('Tes', 'Test'));
+    assert(common.phrase_match('tes', 'Stream Test'));
+
+    assert(!common.phrase_match('tests', 'test'));
+    assert(!common.phrase_match('tes', 'hostess'));
+});
diff --git a/frontend_tests/node_tests/search_suggestion.js b/frontend_tests/node_tests/search_suggestion.js
--- a/frontend_tests/node_tests/search_suggestion.js
+++ b/frontend_tests/node_tests/search_suggestion.js
@@ -7,6 +7,7 @@ zrequire('stream_data');
 zrequire('topic_data');
 zrequire('people');
 zrequire('unread');
+zrequire('common');
 var search = zrequire('search_suggestion');
 
 var bob = {
Repository: zulip/zulip

Issue:
slash commands: Add /dark and /light commands.
We have /night and /day, and people are starting to use them.  We should add the aliases /dark and /light.


Discussion:

Resolving patch:
diff --git a/zerver/lib/zcommand.py b/zerver/lib/zcommand.py
--- a/zerver/lib/zcommand.py
+++ b/zerver/lib/zcommand.py
@@ -14,18 +14,21 @@ def process_zcommands(content: str, user_profile: UserProfile) -> Dict[str, Any]
         ret = dict()  # type: Dict[str, Any]
         return ret
 
-    if command == 'night':
+    night_commands = ['night', 'dark']
+    day_commands = ['day', 'light']
+
+    if command in night_commands:
         if user_profile.night_mode:
             msg = 'You are still in night mode.'
         else:
-            msg = 'Changed to night mode! To revert night mode, type `/day`.'
+            msg = 'Changed to night mode! To revert night mode, type `%s`.' % (content,)
             do_set_user_display_setting(user_profile, 'night_mode', True)
         ret = dict(msg=msg)
         return ret
 
-    if command == 'day':
+    if command in day_commands:
         if user_profile.night_mode:
-            msg = 'Changed to day mode! To revert day mode, type `/night`.'
+            msg = 'Changed to day mode! To revert day mode, type `%s`.' % (content,)
             do_set_user_display_setting(user_profile, 'night_mode', False)
         else:
             msg = 'You are still in day mode.'


Test patch:
Repository: zulip/zulip

Issue:
Fix timezone display format in "new login" emails
I just got this email when testing:

```
Login details:

Server: http://asdasf.zulipdev.com:9991

Account: tabbott@zulipchat.com

Time: Friday, August 03, 2018 at 03:35PM America/Los_Angeles

Device: Chrome on Linux.

IP Address: 127.0.0.1
```

We should display the timezone as e.g. `-05:00`, not with a location.

Tagging as a priority, because we've seen people be confused into thinking this suggested they were in LA.


Discussion:

Resolving patch:
diff --git a/zerver/signals.py b/zerver/signals.py
--- a/zerver/signals.py
+++ b/zerver/signals.py
@@ -77,7 +77,8 @@ def email_on_new_login(sender: Any, user: UserProfile, request: Any, **kwargs: A
         if user_tz == '':
             user_tz = timezone_get_current_timezone_name()
         local_time = timezone_now().astimezone(get_timezone(user_tz))
-        context['login_time'] = local_time.strftime('%A, %B %d, %Y at %I:%M%p ') + user_tz
+        utc_offset = local_time.strftime('%z')
+        context['login_time'] = local_time.strftime('%A, %B %d, %Y at %I:%M%p ') + utc_offset
         context['device_ip'] = request.META.get('REMOTE_ADDR') or _("Unknown IP address")
         context['device_os'] = get_device_os(user_agent)
         context['device_browser'] = get_device_browser(user_agent)


Test patch:
diff --git a/zerver/tests/test_new_users.py b/zerver/tests/test_new_users.py
--- a/zerver/tests/test_new_users.py
+++ b/zerver/tests/test_new_users.py
@@ -33,7 +33,8 @@ def test_send_login_emails_if_send_login_email_setting_is_true(self) -> None:
             utc = get_timezone('utc')
             user_tz = get_timezone(user.timezone)
             mock_time = datetime.datetime(year=2018, month=1, day=1, tzinfo=utc)
-            reference_time = mock_time.astimezone(user_tz).strftime('%A, %B %d, %Y at %I:%M%p ') + user.timezone
+            utc_offset = mock_time.astimezone(user_tz).strftime('%z')
+            reference_time = mock_time.astimezone(user_tz).strftime('%A, %B %d, %Y at %I:%M%p ') + utc_offset
             with mock.patch('zerver.signals.timezone_now', return_value=mock_time):
                 self.client_post("/accounts/login/", info={"username": user.email, "password": password},
                                  HTTP_USER_AGENT=firefox_windows)
Repository: zulip/zulip

Issue:
hipchat conversion: Handle account_type of "guest".
Our original sample data didn't include this, but it's pretty easy to map.  Make sure `is_realm_admin` gets set to `False` and `is_guest` gets sets to `True` when `account_type` is "guest" inside of `convert_user_data` in `zerver/data_import/hipchat.py`.


Discussion:
Sounds good :)

Resolving patch:
diff --git a/zerver/data_import/hipchat.py b/zerver/data_import/hipchat.py
--- a/zerver/data_import/hipchat.py
+++ b/zerver/data_import/hipchat.py
@@ -79,20 +79,13 @@ def convert_user_data(raw_data: List[ZerverFieldsT], realm_id: int) -> List[Zerv
         for d in raw_data
     ]
 
-    def _is_realm_admin(v: str) -> bool:
-        if v == 'user':
-            return False
-        elif v == 'admin':
-            return True
-        else:
-            raise Exception('unexpected value')
-
     def process(in_dict: ZerverFieldsT) -> ZerverFieldsT:
         delivery_email = in_dict['email']
         email = in_dict['email']
         full_name = in_dict['name']
         id = in_dict['id']
-        is_realm_admin = _is_realm_admin(in_dict['account_type'])
+        is_realm_admin = in_dict['account_type'] == 'admin'
+        is_guest = in_dict['account_type'] == 'guest'
         short_name = in_dict['mention_name']
         timezone = in_dict['timezone']
 
@@ -113,6 +106,7 @@ def process(in_dict: ZerverFieldsT) -> ZerverFieldsT:
             id=id,
             is_active=is_active,
             is_realm_admin=is_realm_admin,
+            is_guest=is_guest,
             realm_id=realm_id,
             short_name=short_name,
             timezone=timezone,
diff --git a/zerver/data_import/import_util.py b/zerver/data_import/import_util.py
--- a/zerver/data_import/import_util.py
+++ b/zerver/data_import/import_util.py
@@ -34,6 +34,7 @@ def build_user(avatar_source: str,
                id: int,
                is_active: bool,
                is_realm_admin: bool,
+               is_guest: bool,
                realm_id: int,
                short_name: str,
                timezone: str) -> ZerverFieldsT:
@@ -47,6 +48,7 @@ def build_user(avatar_source: str,
         id=id,
         is_active=is_active,
         is_realm_admin=is_realm_admin,
+        is_guest=is_guest,
         pointer=pointer,
         realm_id=realm_id,
         short_name=short_name,
diff --git a/zproject/settings.py b/zproject/settings.py
--- a/zproject/settings.py
+++ b/zproject/settings.py
@@ -1313,8 +1313,7 @@ def get_dirs(self):
 
 if POPULATE_PROFILE_VIA_LDAP:
     import ldap
-    if (AUTH_LDAP_BIND_DN
-        and ldap.OPT_REFERRALS not in AUTH_LDAP_CONNECTION_OPTIONS):
+    if (AUTH_LDAP_BIND_DN and ldap.OPT_REFERRALS not in AUTH_LDAP_CONNECTION_OPTIONS):
         # The default behavior of python-ldap (without setting option
         # `ldap.OPT_REFERRALS`) is to follow referrals, but anonymously.
         # If our original query was non-anonymous, that's unlikely to


Test patch:
Repository: zulip/zulip

Issue:
upgrade-zulip should respect the same options as upgrade-zulip-from-git
Specifically around things like skipping migrations and puppet. 

This should be easy to do if we refactor the argument parsing login into a function in `zulip_tools.py` and then import it from both places, for example.


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

<!-- areaLabelAddition -->
@zulipbot claim
Hello @shubham-padia, you have been unassigned from this issue because you have not updated this issue or any referenced pull requests for over 14 days.

You can reclaim this issue or claim any other issue by commenting `@zulipbot claim` on that issue.

Thanks for your contributions, and hope to see you again soon!

Resolving patch:
diff --git a/scripts/lib/zulip_tools.py b/scripts/lib/zulip_tools.py
--- a/scripts/lib/zulip_tools.py
+++ b/scripts/lib/zulip_tools.py
@@ -15,9 +15,10 @@
 import time
 import json
 import uuid
+import configparser
 
 if False:
-    from typing import Sequence, Set, Any, Dict, List
+    from typing import Sequence, Set, Any, Dict, List, Optional
 
 DEPLOYMENTS_DIR = "/home/zulip/deployments"
 LOCK_DIR = os.path.join(DEPLOYMENTS_DIR, "lock")
@@ -410,3 +411,18 @@ def assert_running_as_root(strip_lib_from_paths: bool=False) -> None:
     if not is_root():
         print("{} must be run as root.".format(script_name))
         sys.exit(1)
+
+def get_config(config_file, section, key, default_value=""):
+    # type: (configparser.RawConfigParser, str, str, str) -> str
+    if config_file.has_option(section, key):
+        return config_file.get(section, key)
+    return default_value
+
+def get_config_file() -> configparser.RawConfigParser:
+    config_file = configparser.RawConfigParser()
+    config_file.read("/etc/zulip/zulip.conf")
+    return config_file
+
+def get_deploy_options(config_file):
+    # type: (configparser.RawConfigParser) -> List[str]
+    return get_config(config_file, 'deployment', 'deploy_options', "").strip().split()


Test patch:
Repository: zulip/zulip

Issue:
Improve formatting for "arguments" sections with long examples.
The line-wrapping for this endpoint's API documentation looks really ugly:

![image](https://user-images.githubusercontent.com/2746074/47042583-7e303200-d140-11e8-9b4f-d6fc1325dcba.png)

We should either remove the maximum width on "description", or figure out a way to use more than one line for a given endpoint that doesn't look so bad (e.g. having the example be on the next line after the rest of the endpoint description).


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

<!-- areaLabelAddition -->
I think formatting the example JSON  data with a more vertical style would help make the example more readable and reduce the width of the "Example" column to give more room for "Description".
Here's a link to the live docs:

https://chat.zulip.org/api/update-subscription-properties


@zulipbot claim

Resolving patch:
diff --git a/zerver/lib/bugdown/api_arguments_table_generator.py b/zerver/lib/bugdown/api_arguments_table_generator.py
--- a/zerver/lib/bugdown/api_arguments_table_generator.py
+++ b/zerver/lib/bugdown/api_arguments_table_generator.py
@@ -105,7 +105,7 @@ def render_table(self, arguments: List[Dict[str, Any]]) -> List[str]:
         tr = """
 <tr>
   <td><code>{argument}</code></td>
-  <td><code>{example}</code></td>
+  <td class="json-api-example"><code>{example}</code></td>
   <td>{required}</td>
   <td>{description}</td>
 </tr>


Test patch:
Repository: zulip/zulip

Issue:
integrations: Add missing logos.
We seem to be missing logos for Instagram, Twitter, and YouTube.

Also, the logo for Groove is too small (needs to be trimmed), and the logos for irc, matrix, and xkcd are too big (need to add some left and right padding probably to the container that holds the logo). 


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

<!-- areaLabelAddition -->
Isn't `./static/images/integrations/logos/twitter.svg` a Twitter logo?  And ./static/images/integrations/logos/instagram.png for instagram?  ./static/images/integrations/logos/youtube.svg for youtube.

Logos appear for me on zulipchat.com/integrations...
oh weird. Here's what it is for me, on zulipchat.com/integrations:
![image](https://user-images.githubusercontent.com/890911/52904944-a364a800-31e7-11e9-8b1a-31d86bfcb12d.png)

![image](https://user-images.githubusercontent.com/890911/52904949-b1b2c400-31e7-11e9-928c-c372ce67e5fd.png)

![image](https://user-images.githubusercontent.com/890911/52904953-c000e000-31e7-11e9-9200-d2d5f083d725.png)

Check your browser console "Network" tab and/or inspect element on those for what's going on here.  I wouldn't be surprised if this turns out to be a browser extension that's trying to hide social buttons for Twitter/Instagram/Youtube (or something), given that those are exactly the list of integrations that have that kind of feature?

![image](https://user-images.githubusercontent.com/2746074/52905667-27239200-31f2-11e9-967f-fb8de5edea8f.png)


Resolving patch:
diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py
--- a/zerver/lib/integrations.py
+++ b/zerver/lib/integrations.py
@@ -463,7 +463,8 @@ def __init__(self, name: str, *args: Any, **kwargs: Any) -> None:
 BOT_INTEGRATIONS = [
     BotIntegration('github_detail', ['version-control', 'bots'],
                    display_name='GitHub Detail'),
-    BotIntegration('xkcd', ['bots', 'misc'], display_name='xkcd'),
+    BotIntegration('xkcd', ['bots', 'misc'], display_name='xkcd',
+                   logo='static/images/integrations/logos/xkcd.png'),
 ]  # type: List[BotIntegration]
 
 HUBOT_INTEGRATIONS = [


Test patch: