peterbe: Note that I edited the PR title and PR description so GitHub would know to automatically close the issue when this PR lands. 
diff --git a/hashin.py b/hashin.py
index a053ca1..515e037 100755
--- a/hashin.py
+++ b/hashin.py
@@ -387,43 +387,45 @@ def amend_requirements_content(requirements, all_new_lines):
 
     padding = " " * 4
 
-    def is_different_lines(package, new_lines):
-        # This assumes that for sure the package is already mentioned in the old
-        # requirements. Now we just need to double-check that they really are
+    def is_different_lines(old_lines, new_lines, indent):
+        # This assumes that the package is already mentioned in the old
+        # requirements. Now we just need to double-check that its lines are
         # different.
         # The 'new_lines` is what we might intend to replace it with.
-        lines = set()
-        for line in requirements.splitlines():
-            if regex.search(line):
-                lines.add(line.strip(" \\"))
-            elif lines and line.startswith(padding):
-                lines.add(line.strip(" \\"))
-            elif lines:
-                break
-        return lines != set([x.strip(" \\") for x in new_lines.splitlines()])
-
-    for package, old_name, new_lines in all_new_lines:
+        old = set([l.strip(" \\") for l in old_lines])
+        new = set([indent + x.strip(" \\") for x in new_lines])
+        return old != new
+
+    for package, old_name, new_text in all_new_lines:
         regex = re.compile(
-            r"^{0}(\[.*\])?==".format(re.escape(old_name)), re.IGNORECASE | re.MULTILINE
+            r"^(?P<indent>[ \t]*){0}(\[.*\])?==".format(re.escape(old_name)),
+            re.IGNORECASE | re.MULTILINE,
         )
         # if the package wasn't already there, add it to the bottom
-        if not regex.search(requirements):
+        match = regex.search(requirements)
+        if not match:
             # easy peasy
             if requirements:
                 requirements = requirements.strip() + "\n"
-            requirements += new_lines.strip() + "\n"
-        elif is_different_lines(package, new_lines):
-            # need to replace the existing
+            requirements += new_text.strip() + "\n"
+        else:
+            indent = match.group("indent")
             lines = []
             for line in requirements.splitlines():
                 if regex.search(line):
                     lines.append(line)
-                elif lines and line.startswith(padding):
+                elif lines and line.startswith(indent + padding):
                     lines.append(line)
                 elif lines:
                     break
-            combined = "\n".join(lines + [""])
-            requirements = requirements.replace(combined, new_lines)
+            if is_different_lines(lines, new_text.splitlines(), indent):
+                # need to replace the existing
+                combined = "\n".join(lines + [""])
+                # indent non-empty lines
+                indented = re.sub(
+                    r"^(.+)$", r"{0}\1".format(indent), new_text, flags=re.MULTILINE
+                )
+                requirements = requirements.replace(combined, indented)
 
     return requirements
 

Handle indentation
Would be nice if `hashin` could handle indented files like this one:

https://github.com/liberapay/liberapay.com/blob/master/requirements_base.txt

```
boto3==1.9.85 \
    --hash=sha256:acfd27967cf1ba7f9d83ad6fc2011764541e4c295fe0d896ea7b495cc2f03336 \
    --hash=sha256:96296871863e0245b04931df7dd5c583e53cadbe1d54197829b34b03b0d048a8

    botocore==1.12.85 \
        --hash=sha256:af727d4af0cf1ddbf84eaf1cc9d0160ff066eac7f9e6a2fe6a75ccbed4452c98 \
        --hash=sha256:c381fd05b777f41a608ea0846a8d8ecc32077a83e456d05e824cce8d6b213e32
```

Right now it just inserts a new entry below:
```
$ hashin -r ../../../requirements_base.txt botocore -d
--- Old
+++ New
@@ -258,3 +258,6 @@
     asn1crypto==0.24.0 \
         --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \
         --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49
+botocore==1.12.221 \
+    --hash=sha256:6d49deff062d2ae0f03fc26b56df8b1bb9e8b136657bcd8d84c986a4068fb784 \
+    --hash=sha256:bbee3fdcbe56ca53e2c32c6c12d174fa9b4ffe27b633183c29bd5aec9e200bae
```
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 8c60ba0..6bf9cc6 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -579,6 +579,46 @@ selenium==2.53.1 \
     assert new_lines[2] in result
 
 
+def test_amend_requirements_content_indented():
+    """This test came from https://github.com/peterbe/hashin/issues/112"""
+    requirements = (
+        """
+boto3==1.9.85 \\
+    --hash=sha256:acfd27967cf1ba7f9d83ad6fc2011764541e4c295fe0d896ea7b495cc2f03336 \\
+    --hash=sha256:96296871863e0245b04931df7dd5c583e53cadbe1d54197829b34b03b0d048a8
+
+    botocore==1.12.85 \\
+        --hash=sha256:af727d4af0cf1ddbf84eaf1cc9d0160ff066eac7f9e6a2fe6a75ccbed4452c98 \\
+        --hash=sha256:c381fd05b777f41a608ea0846a8d8ecc32077a83e456d05e824cce8d6b213e32
+    """.strip()
+        + "\n"
+    )
+    expect = (
+        """
+boto3==1.9.85 \\
+    --hash=sha256:acfd27967cf1ba7f9d83ad6fc2011764541e4c295fe0d896ea7b495cc2f03336 \\
+    --hash=sha256:96296871863e0245b04931df7dd5c583e53cadbe1d54197829b34b03b0d048a8
+
+    botocore==1.12.221 \\
+        --hash=sha256:6d49deff062d2ae0f03fc26b56df8b1bb9e8b136657bcd8d84c986a4068fb784 \\
+        --hash=sha256:bbee3fdcbe56ca53e2c32c6c12d174fa9b4ffe27b633183c29bd5aec9e200bae
+    """.strip()
+        + "\n"
+    )
+    new_lines = (
+        "botocore",
+        "botocore",
+        """
+botocore==1.12.221 \\
+    --hash=sha256:6d49deff062d2ae0f03fc26b56df8b1bb9e8b136657bcd8d84c986a4068fb784 \\
+    --hash=sha256:bbee3fdcbe56ca53e2c32c6c12d174fa9b4ffe27b633183c29bd5aec9e200bae
+    """.strip()
+        + "\n",
+    )
+    result = hashin.amend_requirements_content(requirements, [new_lines])
+    assert result == expect
+
+
 def test_run(murlopen, tmpfile, capsys):
     def mocked_get(url, **options):
         if url == "https://pypi.org/pypi/hashin/json":

pip install -e .[dev]
parse_log_pytest
pytest
pytest mock
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
backports.tarfile==1.2.0
cachetools==5.5.2
certifi==2025.1.31
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.1
colorama==0.4.6
cryptography==44.0.2
distlib==0.3.9
docutils==0.21.2
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
filelock==3.18.0
-e git+https://github.com/peterbe/hashin.git@10a7048202c27d05befd8c2391e3701069d467ef#egg=hashin
id==1.5.0
idna==3.10
importlib_metadata==8.6.1
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
jeepney==0.9.0
keyring==25.6.0
markdown-it-py==3.0.0
mdurl==0.1.2
mock==5.2.0
more-itertools==10.6.0
nh3==0.2.21
packaging @ file:///croot/packaging_1734472117206/work
pip-api==0.0.34
platformdirs==4.3.7
pluggy @ file:///croot/pluggy_1733169602837/work
pycparser==2.22
Pygments==2.19.1
pyproject-api==1.9.0
pytest @ file:///croot/pytest_1738938843180/work
readme_renderer==44.0
requests==2.32.3
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==14.0.0
SecretStorage==3.3.3
tomli==2.2.1
tox==4.25.0
twine==6.1.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3
zipp==3.21.0

name: hashin
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - backports-tarfile==1.2.0
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cffi==1.17.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - colorama==0.4.6
      - cryptography==44.0.2
      - distlib==0.3.9
      - docutils==0.21.2
      - filelock==3.18.0
      - id==1.5.0
      - idna==3.10
      - importlib-metadata==8.6.1
      - jaraco-classes==3.4.0
      - jaraco-context==6.0.1
      - jaraco-functools==4.1.0
      - jeepney==0.9.0
      - keyring==25.6.0
      - markdown-it-py==3.0.0
      - mdurl==0.1.2
      - mock==5.2.0
      - more-itertools==10.6.0
      - nh3==0.2.21
      - pip-api==0.0.34
      - platformdirs==4.3.7
      - pycparser==2.22
      - pygments==2.19.1
      - pyproject-api==1.9.0
      - readme-renderer==44.0
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - rfc3986==2.0.0
      - rich==14.0.0
      - secretstorage==3.3.3
      - tomli==2.2.1
      - tox==4.25.0
      - twine==6.1.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - zipp==3.21.0
prefix: /opt/conda/envs/hashin

tests/test_cli.py::test_amend_requirements_content_indented
tests/test_cli.py::test_get_latest_version_simple tests/test_cli.py::test_get_latest_version_only_pre_release tests/test_cli.py::test_get_latest_version_non_pre_release_leading_zeros tests/test_cli.py::test_get_hashes_error tests/test_cli.py::test_non_200_ok_download tests/test_cli.py::test_main_packageerrors_stderr tests/test_cli.py::test_packages_and_update_all tests/test_cli.py::test_packages_and_update_all_with_requirements_file tests/test_cli.py::test_no_packages_and_not_update_all tests/test_cli.py::test_interactive_not_update_all tests/test_cli.py::test_main_version tests/test_cli.py::test_amend_requirements_content_new tests/test_cli.py::test_amend_requirements_different_old_name tests/test_cli.py::test_amend_requirements_content_multiple_merge tests/test_cli.py::test_amend_requirements_content_replacement tests/test_cli.py::test_amend_requirements_content_actually_not_replacement tests/test_cli.py::test_amend_requirements_content_replacement_addition tests/test_cli.py::test_amend_requirements_content_replacement_single_to_multi tests/test_cli.py::test_amend_requirements_content_replacement_2 tests/test_cli.py::test_amend_requirements_content_replacement_amongst_others tests/test_cli.py::test_amend_requirements_content_replacement_amongst_others_2 tests/test_cli.py::test_amend_requirements_content_new_similar_name tests/test_cli.py::test_run tests/test_cli.py::test_canonical_list_of_hashes tests/test_cli.py::test_run_atomic_not_write_with_error_on_last_package tests/test_cli.py::test_run_interactive tests/test_cli.py::test_run_interactive_quit_and_accept_all tests/test_cli.py::test_run_interactive_case_redirect tests/test_cli.py::test_run_without_specific_version tests/test_cli.py::test_run_with_alternate_index_url tests/test_cli.py::test_run_contained_names tests/test_cli.py::test_run_case_insensitive tests/test_cli.py::test_run_update_all tests/test_cli.py::test_run_comments_with_package_spec_patterns tests/test_cli.py::test_run_dry tests/test_cli.py::test_run_dry_multiple_packages tests/test_cli.py::test_run_pep_0496 tests/test_cli.py::test_filter_releases tests/test_cli.py::test_release_url_metadata_python tests/test_cli.py::test_expand_python_version tests/test_cli.py::test_get_package_hashes tests/test_cli.py::test_get_package_hashes_from_alternate_index_url tests/test_cli.py::test_get_package_hashes_package_not_found tests/test_cli.py::test_get_package_hashes_unknown_algorithm tests/test_cli.py::test_get_package_hashes_without_version tests/test_cli.py::test_with_extras_syntax tests/test_cli.py::test_extras_syntax_edit tests/test_cli.py::test_add_extra_extras_syntax_edit tests/test_cli.py::test_change_extra_extras_syntax_edit tests/test_cli.py::test_remove_extra_extras_syntax_edit tests/test_cli.py::test_interactive_upgrade_request tests/test_cli.py::test_interactive_upgrade_request_repeat_question tests/test_cli.py::test_interactive_upgrade_request_help tests/test_cli.py::test_interactive_upgrade_request_force_yes
hashin.py
hashin.py