diff --git a/doc/src/reference.xml b/doc/src/reference.xml
index 7e4c4ec86e..77062c274f 100644
--- a/doc/src/reference.xml
+++ b/doc/src/reference.xml
@@ -2187,6 +2187,7 @@ import path : native make : native-path make-path ;
+
diff --git a/doc/src/vcs.rst b/doc/src/vcs.rst
new file mode 100644
index 0000000000..38e44ab542
--- /dev/null
+++ b/doc/src/vcs.rst
@@ -0,0 +1,182 @@
+Boost.Build ``vcs`` Module
+==========================
+
+.. contents::
+
+Overview
+--------
+
+The Boost.Build ``vcs`` module exposes a limited subset of version
+control system functionality to Boost.Build projects for a set of
+supported version control system back ends. Currently, Boost.Build
+``vcs`` supports Subversion and Git. Other systems should be
+straightforward to implement.
+
+Usage
+-----
+
+The following example illustrates the use of the ``vcs`` module.
+
+.. code::
+
+ import vcs ;
+
+ import assert ;
+
+ # print the type of version control system and the generated
+ # version string for this project
+ echo [ vcs.type . ]
+ echo [ vcs.generate-version-string . ] ;
+
+ # fetch and checkout the 1.0 reference of a project kept in the Git
+ # version control system
+ vcs.get git : https://example.com/git/path/to/project/root : /path/to/desired/root ;
+ vcs.checkout : /path/to/desired/root : 1.0 ;
+
+ # verify that the URL and reference matches the desired
+ assert.equal [ vcs.root-url /path/to/desired/root ] : https://example.com/git/path/to/desired/root ;
+ assert.equal [ vcs.ref /path/to/desired/root ] : [ vcs.ref /path/to/desired/root : 1.0 ] ;
+
+The `example/vcs <../../example/vcs>`_ directory in the source
+repository contains a working example of the ``vcs`` module.
+
+The `example/vcs-generate-version-string
+<../../example/vcs-generate-version-string>`_ directory in the source
+repository contains the complete source code to generate a version
+string using the ``vcs`` module. The listings below illustrate the
+use of ``vcs.generate-version-string`` to create a
+``version_string.cpp`` file containing the version string. Note that
+the ``print`` module provides a mechanism to ensure that the generated
+file is only modified when the version string actually changes.
+
+.. include:: ../../example/vcs-generate-version-string/jamroot.jam
+ :code:
+
+.. include:: ../../example/vcs-generate-version-string/main.cpp
+ :code:
+
+Reference
+---------
+
+``type ( directory )``
+
+ Returns the type of version control system for the indicated
+ directory, or the empty string if none was detected.
+
+``generate-version-string ( directory )``
+
+ Returns a string uniquely describing the state of the repository at
+ the given directory.
+
+ - When on a tag, all version control systems will return the tag
+ name
+
+ - Otherwise
+
+ - Git: ``---g``
+
+ - Subversion: ``---s``
+
+ The ``generate-version-string`` rule can be used to generate a version
+ string for a program dynamically.
+
+``fetch ( vcs : root-url : directory )``
+
+ Fetches from the URL to the root of the vcs project to the
+ indicated directory using vcs.
+
+``checkout ( directory : symbolic-ref )``
+
+ Checks out the indicated symbolic reference from the repository
+ located at the indicated directory.
+
+``root-url ( directory )``
+
+ Returns the URL to the root of the vcs project located at the
+ indicated directory.
+
+``ref ( directory : symbolic-ref ? )``
+
+ Returns a unique identifier representing the current state of the
+ vcs project located at directory. If the symbolic reference is
+ given, the rule returns the reference of that symbolic reference,
+ not the current state of the project.
+
+Backends Reference
+------------------
+
+``generate-version-string ( directory )``
+
+ Returns the version string as defined for the backend. Note that
+ each backend is required to return the exact tag name if the
+ directory is on a tag. Otherwise, the format is free-form, but it
+ is recommended that it be as close to the Git format for ``git
+ describe`` as possible for maximum information.
+
+``fetch ( root-url : directory )``
+
+ Fetches the from the URL to the root of the vcs project to the
+ indicated directory using the backend.
+
+``checkout ( directory : symbolic-ref )``
+
+ Checks out the indicated symbolic reference from the repository
+ located at the indicated directory.
+
+``root-url ( directory )``
+
+ Returns the URL to the root of the vcs project located at the
+ indicated directory.
+
+``ref ( directory : symbolic-ref ? )``
+
+ Returns a unique identifier representing the current state of the
+ vcs project located at directory. If the symbolic reference is
+ given, the rule returns the reference of that symbolic reference,
+ not the current state of the project.
+
+``is-repository ( directory )``
+
+ Returns true if the directory is controlled by the backend version
+ control system. This can be as complex or as simple as required.
+
+``executable-exists ( )``
+
+ Returns true if the executable required to support the backend
+ exists on the system.
+
+Design
+------
+
+The Boost.Build ``vcs`` module depends on separate backends to
+implement the interface. The backend file should be named
+``vcs-BACKEND.jam`` where ``BACKEND`` is the name of the backend and
+should contain implementations for each of the functions defined
+below.
+
+Currently, there are two supported backends:
+
+- Git
+- Subversion
+
+Note that the only rule that requires the type of version control
+system to be specified is the ``fetch`` rule. The rest of the rules
+detect the version control system from querying the given directory.
+
+Implementation
+--------------
+
+Hopefully, knowing the implementation will not be required to use this
+module, but a link to the implementation and links to the backends are
+included here for reference.
+
+``vcs`` Interface
+~~~~~~~~~~~~~~~~~
+
+- `vcs <../../src/tools/vcs.jam>`_
+
+Backends
+~~~~~~~~
+
+- `vcs-git <../../src/tools/vcs-git.jam>`_
+- `vcs-svn <../../src/tools/vcs-svn.jam>`_
diff --git a/doc/src/vcs.xml b/doc/src/vcs.xml
new file mode 100644
index 0000000000..d7a0943a30
--- /dev/null
+++ b/doc/src/vcs.xml
@@ -0,0 +1,366 @@
+
+
+
+
+
+ vcs
+
+ vcs
+ module
+
+
+
+ Overview
+
+ The Boost.Build vcs module exposes a limited subset
+ of version control system functionality to Boost.Build projects
+ for a set of supported version control system back ends.
+ Currently, Boost.Build vcs supports Subversion and
+ Git. Other systems should be straightforward to implement.
+
+
+
+
+ Usage
+
+ The following example illustrates the use of the vcs module.
+
+
+
+
+ jamroot.jam
+
+import vcs ;
+
+import assert ;
+
+# print the type of version control system and the generated
+# version string for this project
+echo [ vcs.type . ]
+echo [ vcs.generate-version-string . ] ;
+
+# fetch and checkout the 1.0 reference of a project kept in the Git
+# version control system
+vcs.get git : https://example.com/git/path/to/project/root : /path/to/desired/root ;
+vcs.checkout : /path/to/desired/root : 1.0 ;
+
+# verify that the URL and reference matches the desired
+assert.equal [ vcs.root-url /path/to/desired/root ] : https://example.com/git/path/to/desired/root ;
+assert.equal [ vcs.ref /path/to/desired/root ] : [ vcs.ref /path/to/desired/root : 1.0 ] ;
+
+
+
+
+
+ The example/vcs directory in the source
+ repository contains a working example of the vcs module.
+
+
+
+ The
+ example/vcs-generate-version-string directory in the source
+ repository contains the complete source code to generate a version
+ string using the vcs module. The listings below illustrate the
+ use of vcs.generate-version-string to create a
+ version_string.cpp file containing the version string. Note that
+ the print module provides a mechanism to ensure that the generated
+ file is only modified when the version string actually changes.
+
+
+
+
+ jamroot.jam
+
+
+
+
+
+
+
+
+
+ main.cpp
+
+
+
+
+
+
+
+
+
+ Reference
+
+
+
+
+
+ type
+ vcs
+
+ rule type ( directory )
+
+ Returns the type of version control system for the indicated
+ directory, or the empty string if none was detected.
+
+
+
+
+
+ generate-version-string
+ vcs
+
+ rule generate-version-string ( directory )
+
+ Returns a string uniquely describing the state of the repository at
+ the given directory.
+
+
+
+
+ When on a tag, all version control systems will return the tag
+ name.
+
+
+
+
+
+ Otherwise:
+
+
+
+ Git: <nearest-tag-name>-<branch-name>-<commits-since-nearest-tag>-g<commit-id>
+
+
+
+
+ Subversion: -<URL>--s<REV>
+
+
+
+
+
+
+
+
+
+ The generate-version-string rule can be used to generate a version
+ string for a program dynamically.
+
+
+
+
+
+ fetch
+ vcs
+
+ rule fetch ( vcs : root-url : directory )
+
+ Fetches the from the URL to the root of the vcs project to the
+ indicated directory using vcs.
+
+
+
+
+
+ checkout
+ vcs
+
+ rule checkout ( directory : symbolic-ref )
+
+ Checks out the indicated symbolic reference from the repository
+ located at the indicated directory.
+
+
+
+
+
+ root-url
+ vcs
+
+ rule root-url ( directory )
+
+ Returns the URL to the root of the vcs project located at the
+ indicated directory.
+
+
+
+
+
+ ref
+ vcs
+
+ rule ref ( directory : symbolic-ref ? )
+
+ Returns a unique identifier representing the current state of the
+ vcs project located at directory. If the symbolic reference is
+ given, the rule returns the reference of that symbolic reference,
+ not the current state of the project.
+
+
+
+
+
+
+
+
+ Backends Reference
+
+
+
+
+
+ generate-version-string
+ vcs
+
+ rule generate-version-string ( directory )
+
+ Returns the version string as defined for the backend. Note that
+ each backend is required to return the exact tag name if the
+ directory is on a tag. Otherwise, the format is free-form, but it
+ is recommended that it be as close to the Git format for git
+ describe as possible for maximum information.
+
+
+
+
+
+ fetch
+ vcs
+
+ rule fetch ( root-url : directory )
+
+ Fetches the from the URL to the root of the vcs project to the
+ indicated directory using the backend.
+
+
+
+
+
+ checkout
+ vcs
+
+ rule checkout ( directory : symbolic-ref )
+
+ Checks out the indicated symbolic reference from the repository
+ located at the indicated directory.
+
+
+
+
+
+ root-url
+ vcs
+
+ rule root-url ( directory )
+
+ Returns the URL to the root of the vcs project located at the
+ indicated directory.
+
+
+
+
+
+ ref
+ vcs
+
+ rule ref ( directory : symbolic-ref ? )
+
+ Returns a unique identifier representing the current state of the
+ vcs project located at directory. If the symbolic reference is
+ given, the rule returns the reference of that symbolic reference,
+ not the current state of the project.
+
+
+
+
+
+ is-repository
+ vcs
+
+ rule is-repository ( directory )
+
+ Returns true if the directory is controlled by the backend version
+ control system. This can be as complex or as simple as required.
+
+
+
+
+
+ executable-exists
+ vcs
+
+ rule executable-exists ( )
+
+ Returns true if the executable required to support the backend
+ exists on the system.
+
+
+
+
+
+
+
+ Design
+
+ The Boost.Build vcs module depends on separate backends to
+ implement the interface. The backend file should be named
+ vcs-BACKEND.jam where BACKEND is the name of the backend and
+ should contain implementations for each of the functions defined
+ below.
+
+
+
+ Currently, there are two supported backends:
+
+
+ Git
+ Subversion
+
+
+
+
+ Note that the only rule that requires that that the type of version
+ control system is specified is the fetch rule. The rest detect
+ the version control system from querying the given directory.
+
+
+
+
+ Implementation
+
+ Hopefully, knowing the implementation will not be required to use this
+ module, but a link to the implementation and links to the backends are
+ included here for reference.
+
+
+
+ vcs Interface
+
+
+ src/tools/vcs.jam
+
+
+
+
+
+ Backends
+
+
+
+ src/tools/vcs-git.jam
+
+
+ src/tools/vcs-svn.jam
+
+
+
+
+
+
diff --git a/example/vcs-generate-version-string/.gitignore b/example/vcs-generate-version-string/.gitignore
new file mode 100644
index 0000000000..ae3c172604
--- /dev/null
+++ b/example/vcs-generate-version-string/.gitignore
@@ -0,0 +1 @@
+/bin/
diff --git a/example/vcs-generate-version-string/README.rst b/example/vcs-generate-version-string/README.rst
new file mode 100644
index 0000000000..fad935a1c7
--- /dev/null
+++ b/example/vcs-generate-version-string/README.rst
@@ -0,0 +1,16 @@
+Generate a Version String from VCS
+==================================
+
+This example shows how to generate a version string directly from the
+VCS system used to check out Boost.Build using the ``vcs`` module.
+
+This Boost.Build project generates a program to print out the version
+string of the current working directory. It should be run with
+``--verbose-test`` so the result is easy to see.
+
+.. code::
+
+ b2 --verbose-test
+
+Note that the ``version_string.cpp`` file can be found in the ``bin``
+directory tree.
diff --git a/example/vcs-generate-version-string/jamroot.jam b/example/vcs-generate-version-string/jamroot.jam
new file mode 100644
index 0000000000..5079419fc3
--- /dev/null
+++ b/example/vcs-generate-version-string/jamroot.jam
@@ -0,0 +1,30 @@
+# A Jamroot to run a program that prints a generated version string.
+
+import testing ;
+
+import vcs ;
+import path ;
+import print ;
+
+if ! [ path.exists wd-git ]
+{
+ vcs.fetch git : https://github.com/boostorg/build.git : wd-git ;
+}
+vcs.checkout wd-git : 2016.03 ;
+
+# run it to see the output
+run versioned : : : : versioned-run ;
+
+# note that version_string.cpp is generated below
+exe versioned : main.cpp version_string.cpp ;
+
+# generate the version_string.cpp file
+make version_string.cpp : : @generate-file ;
+rule generate-file ( target : sources * : properties * )
+{
+ local v = [ vcs.generate-version-string wd-git ] ;
+
+ print.output $(target) ;
+ print.text "const char * version_string = \"$(v)\";" : true ;
+ print.text "" ;
+}
diff --git a/example/vcs-generate-version-string/main.cpp b/example/vcs-generate-version-string/main.cpp
new file mode 100644
index 0000000000..bad43498c5
--- /dev/null
+++ b/example/vcs-generate-version-string/main.cpp
@@ -0,0 +1,12 @@
+// A program to print the version string.
+#include
+
+extern const char * version_string;
+
+int
+main ()
+{
+ std::cout << version_string << "\n";
+
+ return 0;
+}
diff --git a/example/vcs/.gitignore b/example/vcs/.gitignore
new file mode 100644
index 0000000000..e14fcd1b13
--- /dev/null
+++ b/example/vcs/.gitignore
@@ -0,0 +1,2 @@
+/bin/
+/repositories/
diff --git a/example/vcs/README.rst b/example/vcs/README.rst
new file mode 100644
index 0000000000..84f5da733c
--- /dev/null
+++ b/example/vcs/README.rst
@@ -0,0 +1,27 @@
+Boost.Build vcs Module Demonstration
+====================================
+
+This Boost.Build project provides a demonstration of the functionality
+available from the ``vcs`` module.
+
+It first prints the properties of the VCS system used to maintain this
+repository, then fetches and checks out the Boost.Build source code
+form GitHub using both Git and Subversion and prints the properties of
+each of those repositories.
+
+This will create the following directory tree by checking out the
+Boost.Build source code at the indicated versions.
+
+::
+
+ repositories
+ repositories/git
+ repositories/gitboost-build-boost-1.55.0
+ repositories/gitboost-build-master
+ repositories/gitboost-build-origin-master
+ repositories/gitboost-build-refs-heads-master
+ repositories/gitboost-build-refs-tags-boost-1.55.0
+ repositories/gitboost-build-tags-boost-1.55.0
+ repositories/svn
+ repositories/svn/boost-build-boost-tags-1.55.0
+ repositories/svn/boost-build-trunk
diff --git a/example/vcs/jamroot.jam b/example/vcs/jamroot.jam
new file mode 100644
index 0000000000..d46a30e119
--- /dev/null
+++ b/example/vcs/jamroot.jam
@@ -0,0 +1,13 @@
+import vcs ;
+
+import vcs-helper ;
+
+# fetch and checkout the Boost.Build source directory
+vcs-helper.execute git : https://github.com/boostorg/build.git : master : boost-build-master ;
+vcs-helper.execute git : https://github.com/boostorg/build.git : refs/heads/master : boost-build-refs-heads-master ;
+vcs-helper.execute git : https://github.com/boostorg/build.git : origin/master : boost-build-origin-master ;
+vcs-helper.execute git : https://github.com/boostorg/build.git : boost-1.55.0 : boost-build-boost-1.55.0 ;
+vcs-helper.execute git : https://github.com/boostorg/build.git : tags/boost-1.55.0 : boost-build-tags-boost-1.55.0 ;
+vcs-helper.execute git : https://github.com/boostorg/build.git : refs/tags/boost-1.55.0 : boost-build-refs-tags-boost-1.55.0 ;
+vcs-helper.execute svn : https://github.com/boostorg/build.git : trunk : boost-build-trunk ;
+vcs-helper.execute svn : https://github.com/boostorg/build.git : tags/boost-1.55.0 : boost-build-boost-tags-1.55.0 ;
diff --git a/example/vcs/vcs-helper.jam b/example/vcs/vcs-helper.jam
new file mode 100644
index 0000000000..ab0dbd59bc
--- /dev/null
+++ b/example/vcs/vcs-helper.jam
@@ -0,0 +1,35 @@
+import path ;
+
+import vcs ;
+
+# fetch, checkout, and analyze a vcs repository
+rule execute ( vcs : root-url : symbolic-ref : path )
+{
+ echo "------------------------------------------------------------------------------" ;
+
+ # a place to put the test repositories
+ local tmp = repositories/$(vcs) ;
+
+ local desired-url = $(root-url) ;
+ local desired-symbolic-ref = $(symbolic-ref) ;
+ local desired-dir = $(tmp)/$(path) ;
+
+ if ! [ path.exists $(desired-dir) ]
+ {
+ vcs.fetch $(vcs) : $(desired-url) : $(desired-dir) ;
+ }
+ vcs.checkout $(desired-dir) : $(desired-symbolic-ref) ;
+
+ local actual-vcs = [ vcs.type $(desired-dir) ] ;
+ local actual-root-url = [ vcs.root-url $(desired-dir) ] ;
+ local actual-head-ref = [ vcs.ref $(desired-dir) ] ;
+ local actual-symbolic-ref = [ vcs.ref $(desired-dir) : $(desired-symbolic-ref) ] ;
+ local actual-version-string = [ vcs.generate-version-string $(desired-dir) ] ;
+
+ echo "info: analyzing $(desired-dir):" ;
+ echo " type: $(actual-vcs)" ;
+ echo " root URL: $(actual-root-url)" ;
+ echo " head reference: $(actual-head-ref)" ;
+ echo " symbolic reference: $(actual-symbolic-ref)" ;
+ echo " generated version string: $(actual-version-string)" ;
+}
diff --git a/src/tools/vcs-git.jam b/src/tools/vcs-git.jam
new file mode 100644
index 0000000000..5812510c12
--- /dev/null
+++ b/src/tools/vcs-git.jam
@@ -0,0 +1,231 @@
+# Copyright 2016
+#
+# Distributed under the Boost Software License, Version 1.0. (See
+# accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+# Version Control System - Git
+#
+# @todo detect if git is available
+# @todo detect if the current repository is a git repository
+
+import os ;
+import path ;
+import errors ;
+import assert ;
+
+# @todo SHELL commands below need to be abstracted to support all
+# systems.
+if [ os.name ] = VMS
+{
+ errors.error "VMS is not supported at this time." ;
+}
+
+# Returns a version string representing the version of the Git
+# repository.
+#
+# If on a tag:
+#
+#
+#
+# If not on a tag:
+#
+# ---g
+#
+# If the repository is dirty, "-dirty" will be appended.
+#
+rule generate-version-string
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ local v = "" ;
+
+ if ! [ executable-exists ]
+ {
+ errors.user-error "vcs-git: Git executable is not installed" ;
+ }
+
+ if ! [ is-repository $(directory) ]
+ {
+ errors.user-error "vcs-git: $(directory) is not a Git repository." ;
+ }
+
+ if [ os.name ] = NT
+ {
+ v = [ SHELL "(cd $(directory) && git describe --tags --exact-match --dirty) 2> NUL" ] ;
+ }
+ else
+ {
+ v = [ SHELL "(cd $(directory) && git describe --tags --exact-match --dirty) 2> /dev/null" ] ;
+ }
+ v = [ SPLIT_BY_CHARACTERS $(v) : "\n" ] ;
+
+ if $(v) != ""
+ {
+ local m = [ MATCH "^(.+)$" : $(v) ] ;
+ if $(m)
+ {
+ v = $(m[1]) ;
+ }
+ }
+ else
+ {
+ local v0 = [ SHELL "(cd $(directory) && git describe --tags --long --dirty)" ] ;
+ v0 = [ SPLIT_BY_CHARACTERS $(v0) : "\n" ] ;
+ local m0 = [ MATCH "^(.+)-([0-9]+)-g([0-9a-fA-F]+)(-dirty)?$" : $(v0) ] ;
+ if ! $(m0)
+ {
+ m0 = "" "" ;
+ }
+
+ local v1 = [ SHELL "(cd $(directory) && git describe --all --long --dirty)" ] ;
+ v1 = [ SPLIT_BY_CHARACTERS $(v1) : "\n" ] ;
+ local m1 = [ MATCH "^(heads|remotes/.+)/(.+)-([0-9]+)-g([0-9a-fA-F]+)(-dirty)?$" : $(v1) ] ;
+ assert.variable-not-empty m1 ;
+
+ v = "$(m0[1])-$(m0[2])-$(m1[2])-g$(m1[4])" ;
+ if $(m1[5])
+ {
+ v = "$(v)-dirty" ;
+ }
+ }
+
+ return $(v) ;
+}
+
+# Fetches from the given url to the given directory.
+#
+# git clone --recurse-submodules $(root-url) $(directory)
+#
+rule fetch
+(
+ root-url : # The root URL of the repository from which to fetch.
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ # @todo check results
+ local r = [ SHELL "git clone --quiet $(root-url) $(directory)" ] ;
+
+ # @todo should work, but doesn't
+ # assert.true path.exists $(directory) ;
+}
+
+# Checks out the indicated symbolic reference for the Git
+# repository at directory.
+#
+# A symbolic reference for Git is a URI based off the 'refs/' base or
+# anything else that looks like a reference or a commit.
+#
+# refs/heads/master
+# refs/tags/1.1.1
+# refs/heads/devel-fixes
+#
+rule checkout
+(
+ directory : # A directory resulting from a Git fetch or checkout.
+ symbolic-ref ? # An optional Git-specific symbolic reference.
+)
+{
+ symbolic-ref = [ normalize-symbolic-ref $(symbolic-ref) ] ;
+
+ # check errors, etc.
+ local r = [ SHELL "(cd $(directory) && git checkout --quiet $(symbolic-ref))" ] ;
+ local s = [ SHELL "(cd $(directory) && git submodule --quiet update --init --recursive)" ] ;
+}
+
+# Returns the root URL of the Git repository at the given directory.
+#
+rule root-url
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ local u = ;
+
+ local output = [ SHELL "(cd $(directory) && git remote -v)" ] ;
+ local lines = [ SPLIT_BY_CHARACTERS $(output) : "\n" ] ;
+ for local line in $(lines)
+ {
+ local m = [ MATCH "^origin[ ]+(.+) .fetch." : $(line) ] ;
+ if $(m)
+ {
+ assert.equal $(u) : ;
+
+ u = $(m[0]) ;
+ }
+ }
+
+ assert.variable-not-empty u ;
+
+ return $(u) ;
+}
+
+# Returns the reference for the symbolic reference requested or HEAD
+# if it is empty for the git repository at directory.
+#
+# For a Git repository, a reference is the SHA-1.
+#
+# deed12131a3334df4322
+#
+rule ref
+(
+ directory : # A directory resulting from a fetch or checkout.
+ symbolic-ref ? # An optional Git-specific symbolic reference.
+)
+{
+ if ! $(symbolic-ref)
+ {
+ symbolic-ref = HEAD ;
+ }
+ symbolic-ref = [ normalize-symbolic-ref $(symbolic-ref) ] ;
+
+ return [ SHELL "(cd $(directory) && git log -n 1 --pretty=format:\"%H\" $(symbolic-ref))" ] ;
+}
+
+# Returns true if the given directory is a Git repository.
+#
+rule is-repository
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ # @todo is there a better way?
+ return [ path.exists "$(directory)/.git" ] ;
+}
+
+# Return true if the Git executable exists.
+#
+rule executable-exists ( )
+{
+ # @todo always say true for now
+ return 1 == 1 ;
+}
+
+# Returns a normalized symbolic reference.
+#
+# refs/heads/* -> origin/*
+#
+# refs/tags/* -> *
+#
+local rule normalize-symbolic-ref
+(
+ symbolic-ref # A Git-specific symbolic reference.
+)
+{
+ local m0 = [ MATCH "^(refs/)?tags/(.+)$" : $(symbolic-ref) ] ;
+ if $(m0)
+ {
+ symbolic-ref = $(m0[2]) ;
+ }
+ else
+ {
+ local m1 = [ MATCH "^(refs/)?heads/(.+)$" : $(symbolic-ref) ] ;
+ if $(m1)
+ {
+ symbolic-ref = origin/$(m1[2]) ;
+ }
+ }
+
+ return $(symbolic-ref) ;
+}
diff --git a/src/tools/vcs-svn.jam b/src/tools/vcs-svn.jam
new file mode 100644
index 0000000000..d94a53947a
--- /dev/null
+++ b/src/tools/vcs-svn.jam
@@ -0,0 +1,270 @@
+# Copyright 2016
+#
+# Distributed under the Boost Software License, Version 1.0. (See
+# accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+# Version Control System - Subversion
+#
+# @todo detect if svn is available
+# @todo detect if the current repository is a Subversion repository
+
+import os ;
+import path ;
+import errors ;
+import assert ;
+
+# @todo SHELL commands below need to be abstracted to support all
+# systems.
+if [ os.name ] = VMS
+{
+ errors.error "VMS is not supported at this time." ;
+}
+
+# Generates a version string from the Subversion repository assuming a
+# standard repository layout.
+#
+# If on a tag:
+#
+#
+#
+# If not on a tag:
+#
+# @todo work to match the Git format, missing nearest tag and commits
+# since nearest tag
+#
+# ---s
+#
+# If the repository is dirty, "-dirty" will be appended.
+#
+rule generate-version-string
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ local v = "" ;
+
+ if ! [ executable-exists ]
+ {
+ errors.user-error "vcs-svn: Subversion executable is not installed" ;
+ }
+
+ if ! [ is-repository $(directory) ]
+ {
+ errors.user-error "vcs-svn: $(directory) is not a Subversion repository." ;
+ }
+
+ local tag = "" ;
+ local branch = "" ;
+ local commits = "0" ;
+ local revision = "" ;
+
+ local url = "" ;
+ local exact_match = "" ;
+
+ local lines = [ SHELL "svn info $(directory)" ] ;
+
+ lines = [ SPLIT_BY_CHARACTERS $(lines) : "\n" ] ;
+
+ for local line in $(lines)
+ {
+ local urlm = [ MATCH "^URL: (.+)\n$" : $(line) ] ;
+ if $(urlm)
+ {
+ url = $(urlm[1]) ;
+ }
+ local revisionm = [ MATCH "^Revision: ([0-9]+)\n$" : $(line) ] ;
+ if $(revisionm)
+ {
+ revision = $(revisionm[1]) ;
+ }
+ }
+
+ if $(url) != ""
+ {
+ tagm = [ MATCH "^.*/tags/(.+)$" : $(url) ] ;
+ if $(tagm)
+ {
+ exact_match = "exact_match" ;
+
+ tag = $(tagm[1]) ;
+ }
+
+ branchm = [ MATCH "^.*/branches/(.+)$" : $(url) ] ;
+ if $(branchm)
+ {
+ branch = $(branchm[1]) ;
+ }
+ else
+ {
+ trunkm = [ MATCH "^.*/(trunk)$" : $(url) ] ;
+ if $(trunkm)
+ {
+ branch = $(trunkm[1]) ;
+ }
+ }
+ }
+
+ # create the version string
+ if $(exact_match) = "exact_match"
+ {
+ v = $(tag) ;
+ }
+ else
+ {
+ v = $(tag)-$(branch)-$(commits)-s$(revision) ;
+ }
+
+ # check if the working copy is dirty
+ if [ SHELL "cd $(directory) && svn diff" ] != ""
+ {
+ v = "$(v)-dirty" ;
+ }
+
+ return $(v) ;
+}
+
+# Fetches from the given url to the given directory.
+#
+# svn checkout $(root-url)/trunk $(directory)
+#
+rule fetch
+(
+ root-url : # The root URL of the repository from which to fetch.
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ # @todo check results
+
+ # @todo there may not be a trunk, what to do?
+ local r = [ SHELL "svn checkout $(root-url)/trunk $(directory)" ] ;
+
+ # @todo should work, but doesn't
+ # assert.true path.exists $(directory) ;
+}
+
+# Checks out the indicated symbolic reference for the Subversion
+# repository at directory.
+#
+# A symbolic reference for Subversion is a URI based off the root URL.
+#
+# trunk
+# tags/1.1.1
+# branches/devel-fixes
+#
+rule checkout
+(
+ directory : # A directory resulting from a Subversion fetch or checkout.
+ symbolic-ref ? # An optional Subversion-specific symbolic reference.
+)
+{
+ # check errors, etc.
+ local ru = [ root-url $(directory) ] ;
+
+ local r = [ SHELL "( cd $(directory) && svn switch $(ru)/$(symbolic-ref) )" ] ;
+}
+
+# Returns the root URL of the Subversion repository at the given
+# directory.
+#
+rule root-url
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ local u = ;
+
+ local output = [ SHELL "( cd $(directory) && svn info )" ] ;
+ local lines = [ SPLIT_BY_CHARACTERS $(output) : "\n" ] ;
+ for local line in $(lines)
+ {
+ m = [ MATCH "^Repository Root: (.+)" : $(line) ] ;
+ if $(m)
+ {
+ assert.equal $(u) : ;
+
+ u = $(m[0]) ;
+ }
+ }
+
+ assert.variable-not-empty u ;
+
+ return $(u) ;
+}
+
+# Returns the reference for the symbol reference if it is not empty or
+# HEAD for the Subversion repository at directory.
+#
+# For a Subversion repository, a reference is the URL fragment beyond
+# the root and the last changed revision.
+#
+# tags/1.1.1@12345
+#
+rule ref
+(
+ directory : # A directory resulting from a fetch or checkout.
+ symbolic-ref ? # An optional Subversion-specific symbolic reference.
+)
+{
+ local ru = [ root-url $(directory) ] ;
+
+ local lines = ;
+ if ! $(symbolic-ref)
+ {
+ lines = [ SHELL "( cd $(directory) && svn info )" ] ;
+ }
+ else
+ {
+ lines = [ SHELL "svn info $(ru)/$(symbolic-ref)" ] ;
+ }
+
+ assert.variable-not-empty lines ;
+
+ local r = ;
+ local sr = ;
+
+ for local line in $(lines)
+ {
+ line = [ SPLIT_BY_CHARACTERS $(line) : "\n" ] ;
+
+ m0 = [ MATCH "^Last Changed Rev: (.+)" : $(line) ] ;
+ if $(m0)
+ {
+ assert.equal $(r) : ;
+
+ r = $(m0[0]) ;
+ }
+
+ m1 = [ MATCH "^URL: $(ru)/(.+)" : $(line) ] ;
+ if $(m1)
+ {
+ assert.equal $(sr) : ;
+
+ sr = $(m1[0]) ;
+ }
+ }
+
+ assert.variable-not-empty r ;
+ assert.variable-not-empty sr ;
+
+ return $(sr)@$(r) ;
+}
+
+# Returns true if the given directory is a Subversion repository.
+#
+rule is-repository
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ # @todo is there a better way?
+ return [ path.exists "$(directory)/.svn" ] ;
+}
+
+# Return true if the Subversion executable exists.
+#
+rule executable-exists ( )
+{
+ # @todo always say true for now
+ return 1 == 1 ;
+}
diff --git a/src/tools/vcs.jam b/src/tools/vcs.jam
new file mode 100644
index 0000000000..78f26e2f09
--- /dev/null
+++ b/src/tools/vcs.jam
@@ -0,0 +1,253 @@
+# Copyright 2016
+#
+# Distributed under the Boost Software License, Version 1.0. (See
+# accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+# Version Control System module
+#
+# Overview
+#
+# The Boost.Build ``vcs`` module exposes a limited subset of version
+# control system functionality to Boost.Build projects for a set of
+# supported version control system back ends. Currently, Boost.Build
+# ``vcs`` supports Subversion and Git. Other systems should be
+# straightforward to implement.
+#
+# Usage
+#
+# An example Boost.Build project illustrating the vcs interface is shown
+# below.
+#
+# ::
+#
+# import vcs ;
+#
+# import assert ;
+#
+# # print the type of version control system and the generated
+# # version string for this project
+# echo [ vcs.type . ]
+# echo [ vcs.generate-version-string . ] ;
+#
+# # fetch and checkout the 1.0 reference of a project kept in the Git
+# # version control system
+# vcs.get git : https://example.com/git/path/to/project/root : /path/to/desired/root ;
+# vcs.checkout : /path/to/desired/root : 1.0 ;
+#
+# # verify that the URL and reference matches the desired
+# assert.equal [ vcs.root-url /path/to/desired/root ] : https://example.com/git/path/to/desired/root ;
+# assert.equal [ vcs.ref /path/to/desired/root ] : [ vcs.ref /path/to/desired/root : 1.0 ] ;
+#
+# Also, see the `example <../../example/vcs>`_ for an exhaustive example.
+
+import path ;
+import errors ;
+import assert ;
+
+debugging-enable = ;
+
+# The list of version control systems supported by this module.
+#
+supported-vcs = git svn ;
+
+for vcs in $(supported-vcs)
+{
+ import vcs-$(vcs) ;
+}
+
+# Returns the type of version control system for the indicated
+# directory, or the empty string if none was detected.
+#
+rule type
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ t = ;
+ for vcs in $(supported-vcs)
+ {
+ if ! $(t) && [ vcs-$(vcs).is-repository $(directory) ]
+ {
+ t = $(vcs) ;
+ }
+ }
+
+ if ! $(t)
+ {
+ errors.error "unknown vcs system at $(directory)" ;
+ }
+
+ return $(t) ;
+}
+
+# Returns a string uniquely describing the state of the repository at
+# the given directory.
+#
+# - When on a tag, all version control systems will return the tag
+# name
+#
+# - Otherwise
+#
+# - Git: ---g
+#
+# - Subversion: ---s
+#
+# The ``generate-version-string`` rule can be used to generate a version
+# string for a program dynamically. The example below shows how to use
+# this to create a ``version_string.cpp`` file containing the version
+# string. The ``print`` module provides a mechanism to ensure that the
+# generated file is only modified when the version string actually
+# changes.
+#
+# Also, see the `example <../../example/vcs-generate-version-string>`_ for
+# an complete example.
+#
+# ::
+#
+# # A Jamroot to run a program that prints a generated version string.
+#
+# import testing ;
+#
+# import vcs ;
+# import print ;
+#
+# path-constant working-directory-root : ../.. ;
+#
+# # run it to see the output
+# run versioned : : : : versioned-run ;
+#
+# # note that version_string.cpp is generated below
+# exe versioned : main.cpp version_string.cpp ;
+#
+# # generate the version_string.cpp file
+# make version_string.cpp : : @generate-file ;
+# rule generate-file ( target : sources * : properties * )
+# {
+# local v = [ vcs.generate-version-string $(working-directory-root) ] ;
+#
+# print.output $(target) ;
+# print.text "const char * version_string = \"$(v)\";" : true ;
+# print.text "" ;
+# }
+#
+# ::
+#
+# // A program to print the version string.
+#
+# #include
+#
+# extern const char * version_string;
+#
+# int
+# main ()
+# {
+# std::cout << "generated version is '" << version_string << "'\n";
+#
+# return 0;
+# }
+#
+rule generate-version-string
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ # @todo need to fix this
+ # if ! [ path.exists $(directory) ]
+ # {
+ # errors.user-error "$(directory) does not exist." ;
+ # }
+
+ vcs = [ type $(directory) ] ;
+ assert.in $(vcs) : $(supported-vcs) ;
+
+ return [ vcs-$(vcs).generate-version-string $(directory) ] ;
+}
+
+# Fetches from the URL to the root of the vcs project to the
+# indicated directory using vcs.
+#
+rule fetch
+(
+ vcs : # The VCS system to use to fetch the root URL.
+ root-url : # The root URL of the repository from which to fetch.
+ directory # The directory into which to fetch the root URL.
+)
+{
+ assert.in $(vcs) : $(supported-vcs) ;
+
+ if [ path.exists $(directory) ]
+ {
+ assert.true type $(directory) : $(vcs) ;
+
+ local current-url = [ vcs-$(vcs).root-url $(directory) ] ;
+
+ if $(current-url) != $(root-url)
+ {
+ errors.error "vcs:$(vcs): $(directory) is at $(current-url) not $(root-url)" ;
+ }
+ }
+ else
+ {
+ if $(debugging-enable)
+ {
+ echo "vcs: fetching $(root-url) to $(directory)" ;
+ }
+
+ local r1 = [ vcs-$(vcs).fetch $(root-url) : $(directory) ] ;
+ }
+}
+
+# Checks out the indicated symbolic reference from the repository
+# located at the indicated directory.
+#
+rule checkout
+(
+ directory : # A directory resulting from a fetch or checkout.
+ symbolic-ref # The VCS-specific symbolic reference to check out.
+)
+{
+ local vcs = [ type $(directory) ] ;
+
+ assert.in $(vcs) : $(supported-vcs) ;
+
+ if $(debugging-enable)
+ {
+ echo "vcs: checking out $(symbolic-ref) at $(directory)" ;
+ }
+
+ local r = [ vcs-$(vcs).checkout $(directory) : $(symbolic-ref) ] ;
+}
+
+# Returns the URL to the root of the vcs project located at the
+# indicated directory.
+#
+rule root-url
+(
+ directory # A directory resulting from a fetch or checkout.
+)
+{
+ local vcs = [ type $(directory) ] ;
+
+ assert.in $(vcs) : $(supported-vcs) ;
+
+ return [ vcs-$(vcs).root-url $(directory) ] ;
+}
+
+# Returns a unique identifier representing the current state of the
+# vcs project located at directory. If the symbolic reference is
+# given, the rule returns the reference of that symbolic reference,
+# not the current state of the project.
+#
+rule ref
+(
+ directory : # A directory resulting from a fetch or checkout.
+ symbolic-ref ? # An optional VCS-specific symbolic reference.
+)
+{
+ local vcs = [ type $(directory) ] ;
+
+ assert.in $(vcs) : $(supported-vcs) ;
+
+ return [ vcs-$(vcs).ref $(directory) : $(symbolic-ref) ] ;
+}
diff --git a/test/example_vcs.py b/test/example_vcs.py
new file mode 100644
index 0000000000..c33fd56838
--- /dev/null
+++ b/test/example_vcs.py
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+
+# Test the 'vcs' example.
+
+import BoostBuild
+
+t = BoostBuild.Tester(use_test_config=False)
+
+t.set_tree("../example/vcs")
+
+t.run_build_system()
+
+#t.expect_output_lines("----------")
+
+t.cleanup()
diff --git a/test/example_vcs_generate_version_string.py b/test/example_vcs_generate_version_string.py
new file mode 100644
index 0000000000..e42bca58cc
--- /dev/null
+++ b/test/example_vcs_generate_version_string.py
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+
+# Test the 'vcs-generate-version-string' example.
+
+import BoostBuild
+
+t = BoostBuild.Tester(use_test_config=False)
+
+t.set_tree("../example/vcs-generate-version-string")
+
+t.run_build_system()
+
+#t.expect_output_lines("2016.03")
+
+t.cleanup()
diff --git a/test/test_all.py b/test/test_all.py
index 56bcf72fda..5e22f38274 100644
--- a/test/test_all.py
+++ b/test/test_all.py
@@ -214,6 +214,8 @@ def reorder_tests(tests, first_test):
"duplicate",
"example_libraries",
"example_make",
+ "example_vcs",
+ "example_vcs_generate_version_string",
"exit_status",
"expansion",
"explicit",
@@ -277,6 +279,7 @@ def reorder_tests(tests, first_test):
"unused",
"use_requirements",
"using",
+ "vcs",
"wrapper",
"wrong_project",
"zlib"
diff --git a/test/vcs.py b/test/vcs.py
new file mode 100644
index 0000000000..c2a1323025
--- /dev/null
+++ b/test/vcs.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+
+import os
+
+import BoostBuild
+
+t = BoostBuild.Tester(use_test_config=False)
+
+t.write("jamroot.jam", """
+import vcs ;
+
+vcs.fetch git : https://github.com/boostorg/build.git : wd-git ;
+
+vcs.checkout wd-git : 2016.03 ;
+
+echo "type:" [ vcs.type wd-git ] ;
+echo "root-url:" [ vcs.root-url wd-git ] ;
+echo "head-ref:" [ vcs.ref wd-git ] ;
+echo "symbolic-ref:" [ vcs.ref wd-git : 2016.03 ] ;
+echo "generate-version-string:" [ vcs.generate-version-string wd-git ] ;
+
+vcs.fetch svn : https://github.com/boostorg/build.git : wd-svn ;
+
+vcs.checkout wd-svn : tags/2016.03 ;
+
+echo "type:" [ vcs.type wd-svn ] ;
+echo "root-url:" [ vcs.root-url wd-svn ] ;
+echo "head-ref:" [ vcs.ref wd-svn ] ;
+echo "symbolic-ref:" [ vcs.ref wd-svn : tags/2016.03 ] ;
+echo "generate-version-string:" [ vcs.generate-version-string wd-svn ] ;
+""")
+
+t.run_build_system()
+
+t.expect_output_lines("type: git")
+t.expect_output_lines("root-url: https://github.com/boostorg/build.git")
+t.expect_output_lines("head-ref: e83838da44f46bbe1f9e07c61dd8d96d13be55df")
+t.expect_output_lines("symbolic-ref: e83838da44f46bbe1f9e07c61dd8d96d13be55df")
+t.expect_output_lines("generate-version-string: 2016.03")
+
+t.expect_output_lines("type: svn")
+t.expect_output_lines("root-url: https://github.com/boostorg/build.git")
+t.expect_output_lines("head-ref: tags/2016.03@12703")
+t.expect_output_lines("symbolic-ref: tags/2016.03@12703")
+t.expect_output_lines("generate-version-string: 2016.03")