c4urself: @florisla -- I like it, haven't had a need for it myself but definitely see why some others might. can we add a test for the replace functionality you were planning on using it for?
florisla: It seems the tests are not compatible to pytest `4.0.0`.  On `3.10.1` it passes with deprecation warnings.
lycantropos: If I understood correctly this will allow us to handle cases like
```
https://cmake.org/files/v3.9/cmake-3.9.5.tar.gz
```
which after bumping minor version becomes
```
https://cmake.org/files/v3.10/cmake-3.10.5.tar.gz
```
?
florisla: If you have a special bumpversion configuration file to track the cmake version, yes.

You can use the parts (major/minor/patch or custom) independently everywhere you like.  So you could also create a commit message like

    Move to cmake major version "3", minor "10", patch "0" from 3/9/5
florisla: I've just rebased on #46 so the unit tests now use 'vcs' as a fixture.
florisla: Rebased on master.

Let me know if you'd like to see other changes.
florisla: Rebased on master.
florisla: I've rebased on master and updated the Readme to reflect the changes (+ some cosmetic fixes).
andrew-glenn: @c4urself - any chance this can be merged? :)
diff --git a/Makefile b/Makefile
index ec555a6..7e411f4 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,9 @@ test:
 	docker-compose build test
 	docker-compose run test
 
+local_test:
+	PYTHONPATH=. py.test tests/
+
 lint:
 	pip install pylint
 	pylint bumpversion
diff --git a/README.md b/README.md
index a8a7454..7e67ee0 100644
--- a/README.md
+++ b/README.md
@@ -144,14 +144,16 @@ General configuration is grouped in a `[bumpversion]` section.
 
   The name of the tag that will be created. Only valid when using `--tag` / `tag = True`.
 
-  This is templated using the [Python Format String Syntax](http://docs.python.org/2/library/string.html#format-string-syntax)
+  This is templated using the [Python Format String Syntax](http://docs.python.org/2/library/string.html#format-string-syntax).  
   Available in the template context are `current_version` and `new_version`
-  as well as all environment variables (prefixed with `$`). You can also use
-  the variables `now` or `utcnow` to get a current timestamp. Both accept
+  as well as `current_[part]` and `new_[part]` (e.g. '`current_major`'
+  or '`new_patch`').
+  In addtion, all environment variables are exposed, prefixed with `$`.  
+  You can also use the variables `now` or `utcnow` to get a current timestamp. Both accept
   datetime formatting (when used like as in `{now:%d.%m.%Y}`).
 
-  Also available as a command line flag, `--tag-name` (e.g. `bump2version --message 'Jenkins Build
-  {$BUILD_NUMBER}: {new_version}' patch`).
+  Also available as command-line flag `tag_name`.  Example usage:  
+  `bump2version --tag_name 'release-{new_version}' patch`
 
 #### `commit = (True | False)`
   _**[optional]**_<br />
@@ -176,14 +178,16 @@ General configuration is grouped in a `[bumpversion]` section.
 
   The commit message to use when creating a commit. Only valid when using `--commit` / `commit = True`.
 
-  This is templated using the [Python Format String Syntax](http://docs.python.org/2/library/string.html#format-string-syntax)
+  This is templated using the [Python Format String Syntax](http://docs.python.org/2/library/string.html#format-string-syntax).  
   Available in the template context are `current_version` and `new_version`
-  as well as all environment variables (prefixed with `$`). You can also use
-  the variables `now` or `utcnow` to get a current timestamp. Both accept
+  as well as `current_[part]` and `new_[part]` (e.g. '`current_major`'
+  or '`new_patch`').
+  In addition, all environment variables are exposed, prefixed with `$`.  
+  You can also use the variables `now` or `utcnow` to get a current timestamp. Both accept
   datetime formatting (when used like as in `{now:%d.%m.%Y}`).
-
-  Also available as `--message` (e.g.: `bump2version --message
-  '[{now:%Y-%m-%d}] Jenkins Build {$BUILD_NUMBER}: {new_version}' patch`)
+  
+  Also available as command-line flag `--message`.  Example usage:  
+  `bump2version --message '[{now:%Y-%m-%d}] Jenkins Build {$BUILD_NUMBER}: {new_version}' patch`)
 
 
 ### Configuration file -- Part specific configuration
@@ -277,7 +281,7 @@ This configuration is in the section: `[bumpversion:file:…]`
   Available in the template context are parsed values of the named groups
   specified in `parse =` as well as all environment variables (prefixed with
   `$`).
-
+  
   Can be specified multiple times, bumpversion will try the serialization
   formats beginning with the first and choose the last one where all values can
   be represented like this::
@@ -286,8 +290,8 @@ This configuration is in the section: `[bumpversion:file:…]`
       {major}.{minor}
       {major}
 
-  Given the example above, the new version *1.9* it will be serialized as
-  `1.9`, but the version *2.0* will be serialized as `2`.
+  Given the example above, the new version `1.9` will be serialized as
+  `1.9`, but the version `2.0` will be serialized as `2`.
 
   Also available as `--serialize`. Multiple values on the command line are
   given like `--serialize {major}.{minor} --serialize {major}`
@@ -297,7 +301,7 @@ This configuration is in the section: `[bumpversion:file:…]`
 
   Template string how to search for the string to be replaced in the file.
   Useful if the remotest possibility exists that the current version number
-  might be multiple times in the file and you mean to only bump one of the
+  might be present multiple times in the file and you mean to only bump one of the
   occurences. Can be multiple lines, templated using [Python Format String Syntax](http://docs.python.org/2/library/string.html#format-string-syntax)
 
 #### `replace =`
diff --git a/bumpversion/cli.py b/bumpversion/cli.py
index ac2d05d..5a7d531 100644
--- a/bumpversion/cli.py
+++ b/bumpversion/cli.py
@@ -115,7 +115,7 @@ def main(original_args=None):
 
     # commit and tag
     if vcs:
-        context = _commit_to_vcs(files, context, config_file, config_file_exists, vcs, args)
+        context = _commit_to_vcs(files, context, config_file, config_file_exists, vcs, args, current_version, new_version)
         _tag_in_vcs(vcs, context, args)
 
 
@@ -628,7 +628,7 @@ def _update_config_file(
         )
 
 
-def _commit_to_vcs(files, context, config_file, config_file_exists, vcs, args):
+def _commit_to_vcs(files, context, config_file, config_file_exists, vcs, args, current_version, new_version):
     commit_files = [f.path for f in files]
     if config_file_exists:
         commit_files.append(config_file)
@@ -652,9 +652,17 @@ def _commit_to_vcs(files, context, config_file, config_file_exists, vcs, args):
         if do_commit:
             vcs.add_path(path)
 
-    context["current_version"] = args.current_version
-    context["new_version"] = args.new_version
+    context = {
+        "current_version": args.current_version,
+        "new_version": args.new_version,
+    }
+    context.update(time_context)
+    context.update(prefixed_environ())
+    context.update({'current_' + part: current_version[part].value for part in current_version})
+    context.update({'new_' + part: new_version[part].value for part in new_version})
+
     commit_message = args.message.format(**context)
+
     logger.info(
         "%s to %s with message '%s'",
         "Would commit" if not do_commit else "Committing",

Is exposing major/minor/patch in 'replace' desirable?
Hi,

I've been surprised a couple of times that I can use `{major}`, `{minor}` and `{patch}` placeholders in `serialize`, but not in `replace`.  In replace, only `new_version` is available, which is already formatted by the `serialize` format.

Is this a design choice or would you be open to change that?

I would definitely like to have to flexibility to choose between new_version and major/minor/patch.
diff --git a/tests/test_cli.py b/tests/test_cli.py
index e678ea1..521804a 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -778,6 +778,74 @@ tag_name: from-{current_version}-to-{new_version}""")
     assert b'from-400.0.0-to-401.0.0' in tag_out
 
 
+def test_all_parts_in_message_and_serialize_and_tag_name_from_config_file(tmpdir, capsys, vcs):
+    """
+    Ensure that major/minor/patch *and* custom parts can be used everywhere.
+
+    - As [part] in 'serialize'.
+    - As new_[part] and previous_[part] in 'message'.
+    - As new_[part] and previous_[part] in 'tag_name'.
+
+    In message and tag_name, also ensure that new_version and
+    current_version are correct.
+    """
+    tmpdir.chdir()
+    check_call([vcs, "init"])
+    tmpdir.join("VERSION").write("400.1.2.101")
+    check_call([vcs, "add", "VERSION"])
+    check_call([vcs, "commit", "-m", "initial commit"])
+
+    tmpdir.join(".bumpversion.cfg").write(r"""[bumpversion]
+current_version: 400.1.2.101
+new_version: 401.2.3.102
+parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).(?P<custom>\d+)
+serialize = {major}.{minor}.{patch}.{custom}
+commit: True
+tag: True
+message: {current_version}/{current_major}.{current_minor}.{current_patch} custom {current_custom} becomes {new_version}/{new_major}.{new_minor}.{new_patch} custom {new_custom}
+tag_name: from-{current_version}-aka-{current_major}.{current_minor}.{current_patch}-custom-{current_custom}-to-{new_version}-aka-{new_major}.{new_minor}.{new_patch}-custom-{new_custom}
+
+[bumpversion:part:custom]
+""")
+
+    main(['major', 'VERSION'])
+
+    log = check_output([vcs, "log", "-p"])
+    assert b'400.1.2.101/400.1.2 custom 101 becomes 401.2.3.102/401.2.3 custom 102' in log
+
+    tag_out = check_output([vcs, {"git": "tag", "hg": "tags"}[vcs]])
+    assert b'from-400.1.2.101-aka-400.1.2-custom-101-to-401.2.3.102-aka-401.2.3-custom-102' in tag_out
+
+
+def test_all_parts_in_replace_from_config_file(tmpdir, capsys, vcs):
+    """
+    Ensure that major/minor/patch *and* custom parts can be used in 'replace'.
+    """
+    tmpdir.chdir()
+    check_call([vcs, "init"])
+    tmpdir.join("VERSION").write("my version is 400.1.2.101\n")
+    check_call([vcs, "add", "VERSION"])
+    check_call([vcs, "commit", "-m", "initial commit"])
+
+    tmpdir.join(".bumpversion.cfg").write(r"""[bumpversion]
+current_version: 400.1.2.101
+new_version: 401.2.3.102
+parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).(?P<custom>\d+)
+serialize = {major}.{minor}.{patch}.{custom}
+commit: True
+tag: False
+
+[bumpversion:part:custom]
+
+[bumpversion:VERSION]
+search = my version is {current_version}
+replace = my version is {new_major}.{new_minor}.{new_patch}.{new_custom}""")
+
+    main(['major', 'VERSION'])
+    log = check_output([vcs, "log", "-p"])
+    assert b'+my version is 401.2.3.102' in log
+
+
 def test_unannotated_tag(tmpdir, vcs):
     tmpdir.chdir()
     check_call([vcs, "init"])

pip install -e .
parse_log_pytest
pytest
pytest mock testfixtures
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
-e git+https://github.com/c4urself/bump2version.git@464802f3a54f1700671a27d02f56ef38ef6b15ae#egg=bump2version
certifi @ file:///croot/certifi_1671487769961/work/certifi
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
mock==5.2.0
packaging @ file:///croot/packaging_1671697413597/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pytest==7.1.2
testfixtures==8.3.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
zipp @ file:///croot/zipp_1672387121353/work

name: bump2version
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
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_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=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - mock==5.2.0
      - testfixtures==8.3.0
prefix: /opt/conda/envs/bump2version

tests/test_cli.py::test_all_parts_in_message_and_serialize_and_tag_name_from_config_file[git] tests/test_cli.py::test_all_parts_in_message_and_serialize_and_tag_name_from_config_file[hg]
tests/test_cli.py::test_usage_string tests/test_cli.py::test_usage_string_fork tests/test_cli.py::test_regression_help_in_work_dir[git] tests/test_cli.py::test_regression_help_in_work_dir[hg] tests/test_cli.py::test_defaults_in_usage_with_config tests/test_cli.py::test_missing_explicit_config_file tests/test_cli.py::test_simple_replacement tests/test_cli.py::test_simple_replacement_in_utf8_file tests/test_cli.py::test_config_file tests/test_cli.py::test_default_config_files[.bumpversion.cfg] tests/test_cli.py::test_default_config_files[setup.cfg] tests/test_cli.py::test_multiple_config_files tests/test_cli.py::test_config_file_is_updated tests/test_cli.py::test_dry_run[git] tests/test_cli.py::test_dry_run[hg] tests/test_cli.py::test_bump_version tests/test_cli.py::test_bump_version_custom_main tests/test_cli.py::test_bump_version_custom_parse_serialize_configfile tests/test_cli.py::test_bumpversion_custom_parse_semver tests/test_cli.py::test_dirty_work_dir[git] tests/test_cli.py::test_dirty_work_dir[hg] tests/test_cli.py::test_force_dirty_work_dir[git] tests/test_cli.py::test_force_dirty_work_dir[hg] tests/test_cli.py::test_bump_major tests/test_cli.py::test_commit_and_tag[git] tests/test_cli.py::test_commit_and_tag[hg] tests/test_cli.py::test_commit_and_tag_with_configfile[git] tests/test_cli.py::test_commit_and_tag_with_configfile[hg] tests/test_cli.py::test_commit_and_not_tag_with_configfile[git-[bumpversion]\ncommit tests/test_cli.py::test_commit_and_not_tag_with_configfile[hg-[bumpversion]\ncommit tests/test_cli.py::test_commit_explicitly_false[git] tests/test_cli.py::test_commit_explicitly_false[hg] tests/test_cli.py::test_commit_configfile_true_cli_false_override[git] tests/test_cli.py::test_commit_configfile_true_cli_false_override[hg] tests/test_cli.py::test_bump_version_environment tests/test_cli.py::test_current_version_from_tag[git] tests/test_cli.py::test_current_version_from_tag_written_to_config_file[git] tests/test_cli.py::test_distance_to_latest_tag_as_part_of_new_version[git] tests/test_cli.py::test_override_vcs_current_version[git] tests/test_cli.py::test_non_existing_file tests/test_cli.py::test_non_existing_second_file tests/test_cli.py::test_read_version_tags_only[git] tests/test_cli.py::test_tag_name[git] tests/test_cli.py::test_tag_name[hg] tests/test_cli.py::test_message_from_config_file[git] tests/test_cli.py::test_message_from_config_file[hg] tests/test_cli.py::test_all_parts_in_replace_from_config_file[git] tests/test_cli.py::test_all_parts_in_replace_from_config_file[hg] tests/test_cli.py::test_unannotated_tag[git] tests/test_cli.py::test_unannotated_tag[hg] tests/test_cli.py::test_annotated_tag[git] tests/test_cli.py::test_annotated_tag[hg] tests/test_cli.py::test_vcs_describe[git] tests/test_cli.py::test_utf8_message_from_config_file[git] tests/test_cli.py::test_utf8_message_from_config_file[hg] tests/test_cli.py::test_commit_and_tag_from_below_vcs_root[git] tests/test_cli.py::test_commit_and_tag_from_below_vcs_root[hg] tests/test_cli.py::test_non_vcs_operations_if_vcs_is_not_installed[git] tests/test_cli.py::test_non_vcs_operations_if_vcs_is_not_installed[hg] tests/test_cli.py::test_serialize_newline tests/test_cli.py::test_multiple_serialize_three_part tests/test_cli.py::test_multiple_serialize_two_part tests/test_cli.py::test_multiple_serialize_two_part_patch tests/test_cli.py::test_multiple_serialize_two_part_patch_configfile tests/test_cli.py::test_log_no_config_file_info_message tests/test_cli.py::test_log_parse_doesnt_parse_current_version tests/test_cli.py::test_log_invalid_regex_exit tests/test_cli.py::test_complex_info_logging tests/test_cli.py::test_subjunctive_dry_run_logging[git] tests/test_cli.py::test_subjunctive_dry_run_logging[hg] tests/test_cli.py::test_log_commit_message_if_no_commit_tag_but_usable_vcs[git] tests/test_cli.py::test_log_commit_message_if_no_commit_tag_but_usable_vcs[hg] tests/test_cli.py::test_listing[git] tests/test_cli.py::test_listing[hg] tests/test_cli.py::test_no_list_no_stdout[git] tests/test_cli.py::test_no_list_no_stdout[hg] tests/test_cli.py::test_bump_non_numeric_parts tests/test_cli.py::test_optional_value_from_documentation tests/test_cli.py::test_python_pre_release_release_post_release tests/test_cli.py::test_part_first_value tests/test_cli.py::test_multi_file_configuration tests/test_cli.py::test_multi_file_configuration2 tests/test_cli.py::test_search_replace_to_avoid_updating_unconcerned_lines tests/test_cli.py::test_search_replace_expanding_changelog tests/test_cli.py::test_search_replace_cli tests/test_cli.py::test_deprecation_warning_files_in_global_configuration tests/test_cli.py::test_deprecation_warning_multiple_files_cli tests/test_cli.py::test_file_specific_config_inherits_parse_serialize tests/test_cli.py::test_multi_line_search_is_found tests/test_cli.py::test_configparser_empty_lines_in_values tests/test_cli.py::test_regression_tag_name_with_hyphens[git] tests/test_cli.py::test_unclean_repo_exception[git] tests/test_cli.py::test_regression_characters_after_last_label_serialize_string tests/test_cli.py::test_regression_dont_touch_capitalization_of_keys_in_config tests/test_cli.py::test_regression_new_version_cli_in_files tests/test_cli.py::test_correct_interpolation_for_setup_cfg_files[.bumpversion.cfg] tests/test_cli.py::test_correct_interpolation_for_setup_cfg_files[setup.cfg] tests/test_cli.py::test_retain_newline[.bumpversion.cfg-\n] tests/test_cli.py::test_retain_newline[.bumpversion.cfg-\r\n] tests/test_cli.py::test_retain_newline[setup.cfg-\n] tests/test_cli.py::test_retain_newline[setup.cfg-\r\n] tests/test_cli.py::TestSplitArgsInOptionalAndPositional::test_all_optional tests/test_cli.py::TestSplitArgsInOptionalAndPositional::test_all_positional tests/test_cli.py::TestSplitArgsInOptionalAndPositional::test_no_args tests/test_cli.py::TestSplitArgsInOptionalAndPositional::test_short_optionals tests/test_cli.py::TestSplitArgsInOptionalAndPositional::test_1optional_2positional tests/test_cli.py::TestSplitArgsInOptionalAndPositional::test_2optional_1positional tests/test_cli.py::TestSplitArgsInOptionalAndPositional::test_2optional_mixed_2positional
Makefile README.md bumpversion/cli.py
Makefile README.md bumpversion/cli.py