Repository: c4urself/bump2version

Issue:
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.

Discussion:
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? :)

Patch:
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",


Test 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"])