aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-03-20 14:01:38 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-03-20 14:01:38 +0200
commit10ec1ac16b5ec2577b94e8311e48ee3c7db9fb78 (patch)
treec23d7edabe624448b20b835af74bcea81cdf39bd
parent4cc966d1468f6ce59600517e44d4817c0905210f (diff)
Come up with better version for uncommitted snapshots
Instead of leaving it as .z we now take the date of the previous commit and increment it by a second. The main benefit of doing it this way is that once committed, the new version does not "jump back" behind .z.
-rw-r--r--build2/version/init.cxx20
-rw-r--r--build2/version/module.hxx10
-rw-r--r--build2/version/snapshot-git.cxx51
-rw-r--r--build2/version/snapshot.hxx1
-rw-r--r--doc/manual.cli52
5 files changed, 90 insertions, 44 deletions
diff --git a/build2/version/init.cxx b/build2/version/init.cxx
index 7b8bd01..f793c98 100644
--- a/build2/version/init.cxx
+++ b/build2/version/init.cxx
@@ -163,9 +163,9 @@ namespace build2
}
// If this is the latest snapshot (i.e., the -a.1.z kind), then load the
- // snapshot sn and id (e.g., commit date and id from git). If there is
- // uncommitted stuff, then leave it as .z.
+ // snapshot number and id (e.g., commit date and id from git).
//
+ bool committed (true);
if (v.snapshot () && v.snapshot_sn == standard_version::latest_sn)
{
snapshot ss (extract_snapshot (rs));
@@ -174,7 +174,10 @@ namespace build2
{
v.snapshot_sn = ss.sn;
v.snapshot_id = move (ss.id);
+ committed = ss.committed;
}
+ else
+ committed = false;
}
// Set all the version.* variables.
@@ -215,16 +218,17 @@ namespace build2
set ("version.pre_release_string", v.string_pre_release ());
set ("version.pre_release_number", uint64_t (v.pre_release ()));
- set ("version.snapshot", v.snapshot ()); // bool
- set ("version.snapshot_sn", v.snapshot_sn); // uint64
- set ("version.snapshot_id", v.snapshot_id); // string
- set ("version.snapshot_string", v.string_snapshot ());
+ set ("version.snapshot", v.snapshot ()); // bool
+ set ("version.snapshot_sn", v.snapshot_sn); // uint64
+ set ("version.snapshot_id", v.snapshot_id); // string
+ set ("version.snapshot_string", v.string_snapshot ());
+ set ("version.snapshot_committed", committed); // bool
set ("version.revision", uint64_t (v.revision));
// Create the module.
//
- mod.reset (new module (move (v), move (ds)));
+ mod.reset (new module (move (v), committed, move (ds)));
return true; // Init first (dist.package, etc).
}
@@ -331,7 +335,7 @@ namespace build2
// Complain if this is an uncommitted snapshot.
//
- if (v.snapshot_sn == standard_version::latest_sn)
+ if (v.snapshot () && !m.committed)
fail << "distribution of uncommitted project " << rs.src_path ();
// The plan is simple, re-serialize the manifest into a temporary file
diff --git a/build2/version/module.hxx b/build2/version/module.hxx
index 190d8d7..a5a667a 100644
--- a/build2/version/module.hxx
+++ b/build2/version/module.hxx
@@ -25,13 +25,19 @@ namespace build2
static const string name;
butl::standard_version version;
+ bool committed; // Whether this is a committed snapshot.
+
dependency_constraints dependencies;
const variable* in_symbol = nullptr; // in.symbol
const variable* in_substitution = nullptr; // in.substitution
- module (butl::standard_version v, dependency_constraints d)
- : version (move (v)), dependencies (move (d)) {}
+ module (butl::standard_version v,
+ bool c,
+ dependency_constraints d)
+ : version (move (v)),
+ committed (c),
+ dependencies (move (d)) {}
};
}
}
diff --git a/build2/version/snapshot-git.cxx b/build2/version/snapshot-git.cxx
index b85b47e..751686e 100644
--- a/build2/version/snapshot-git.cxx
+++ b/build2/version/snapshot-git.cxx
@@ -31,9 +31,9 @@ namespace build2
//
{
const char* args[] {"git", "-C", d, "status", "--porcelain", nullptr};
-
- if (!run<string> (3, args, [](string& s) {return move (s);}).empty ())
- return r;
+ r.committed = run<string> (3 /* verbosity */,
+ args,
+ [](string& s) {return move (s);}).empty ();
}
// Now extract the commit id and date. One might think that would be
@@ -45,7 +45,18 @@ namespace build2
//
// Where <len> is the size of <data> and <data> is the output of:
//
- // git cat-file commit ...
+ // git cat-file commit HEAD
+ //
+ // There is also one annoying special case: new repository without any
+ // commits. In this case the above command will fail (with diagnostics
+ // and non-zero exit code) because there is no HEAD. Of course, it can
+ // also fail for other reason (like broken repository) which would be
+ // hard to distinguish. Note, however, that we just ran git status and
+ // it would have most likely failed if this were the case. So here we
+ // (reluctantly) assume that the only reason git cat-file fails is if
+ // there is no HEAD (that we equal with the "new repository" condition
+ // which is, strictly speaking, might not be the case either). So we
+ // suppress any diagnostics, and handle non-zero exit code.
//
string data;
@@ -53,14 +64,16 @@ namespace build2
"git", "-C", d, "cat-file", "commit", "HEAD", nullptr};
process pr (run_start (3 /* verbosity */,
args,
- 0 /* stdin */,
- -1 /* stdout */));
+ 0 /* stdin */,
+ -1 /* stdout */,
+ false /* error */));
+ string l;
try
{
ifdstream is (move (pr.in_ofd), ifdstream::badbit);
- for (string l; !eof (getline (is, l)); )
+ while (!eof (getline (is, l)))
{
data += l;
data += '\n'; // We assume there is always a newline.
@@ -128,16 +141,28 @@ namespace build2
// that.
}
- run_finish (args, pr);
+ if (!run_finish (args, pr, false /* error */, l))
+ {
+ // Presumably new repository without HEAD. Return uncommitted snapshot
+ // with UNIX epoch as timestamp.
+ //
+ r.sn = 19700101000000ULL;
+ r.committed = false;
+ return r;
+ }
if (r.sn == 0)
fail << "unable to extract git commit id/date for " << src_root;
- sha1 cs;
- cs.append ("commit " + to_string (data.size ())); // Includes '\0'.
- cs.append (data.c_str (), data.size ());
-
- r.id.assign (cs.string (), 12); // 12-characters abbreviated commit id.
+ if (r.committed)
+ {
+ sha1 cs;
+ cs.append ("commit " + to_string (data.size ())); // Includes '\0'.
+ cs.append (data.c_str (), data.size ());
+ r.id.assign (cs.string (), 12); // 12-characters abbreviated commit id.
+ }
+ else
+ r.sn++; // Add a second.
return r;
}
diff --git a/build2/version/snapshot.hxx b/build2/version/snapshot.hxx
index b7e6dd7..15f4b59 100644
--- a/build2/version/snapshot.hxx
+++ b/build2/version/snapshot.hxx
@@ -18,6 +18,7 @@ namespace build2
{
uint64_t sn = 0;
string id;
+ bool committed = false;
bool
empty () const {return sn == 0;}
diff --git a/doc/manual.cli b/doc/manual.cli
index 6dd7002..f10e54f 100644
--- a/doc/manual.cli
+++ b/doc/manual.cli
@@ -343,8 +343,9 @@ increment \i{patch} when making binary-compatible changes, \i{minor} when
making source-compatible changes, and \i{major} when making breaking changes.
While the binary compatibility must be set in stone, the source compatibility
rules can sometimes be bent. For example, you may decide to make a breaking
-change in a rarely used interface as part of a minor release. Note also that
-in the context of C++ deciding whether a change is binary-compatible is a
+change in a rarely used interface as part of a minor release (though this is
+probably still a bad idea if your library is widely depended upon). Note also
+that in the context of C++ deciding whether a change is binary-compatible is a
non-trivial task. There are resources that list the rules but no automated
tooling yet. If unsure, increment \i{minor}.
@@ -379,10 +380,11 @@ worse, from the final release. One way to remedy this is to increment the
pre-release number before each publication. However, unless automated, this
will be burdensome and error-prone. Also, there is a real possibility of
running out of version numbers if, for example, we do continuous integration
-by testing (and therefore publishing) each commit.
+by publishing and testing each commit.
To address this, the standard versioning scheme supports \i{snapshot
-pre-releases} with the \i{prerel} component having the following form:
+pre-releases} with the \i{prerel} component having the following extended
+form:
\
(a|b).<num>.<snapsn>[.<snapid>]
@@ -391,13 +393,13 @@ pre-releases} with the \i{prerel} component having the following form:
For example:
\
-1.2.3-a.1.1422564055.340c0a26a5efed1f
+1.2.3-a.1.20180319215815.26efe301f4a7
\
In essence, a snapshot pre-release is after the previous final release but
before the next (\c{a.1} and, perhaps, \c{a.2} in the above example) and
is uniquely identified by the snapshot sequence number (\i{snapsn}) and
-snapshot id (\i{snapid}).
+optional snapshot id (\i{snapid}).
The \i{num} component has the same semantics as in the final pre-releases
except that it can be \c{0}. The \i{snapsn} component should be either the
@@ -408,12 +410,12 @@ uniquely identifies the snapshot. It is not required for version comparison
(\i{snapsn} should be sufficient) and is included for reference. It must not
be longer than 16 characters.
-Where do the snapshot sn and id come from? Normally from the version control
-system. For example, for \c{git}, \i{snapsn} is the commit date (as UNIX
-timestamp in the UTC timezone) and \i{snapid} is a 16-character abbreviated
-commit id. As discussed below, the \c{build2} \c{version} module extracts
-and manages all this information automatically (the use of \c{git} commit
-dates is not without limitations; see below for details).
+Where do the snapshot number and id come from? Normally from the version
+control system. For example, for \c{git}, \i{snapsn} is the commit date in the
+\i{YYYYMMDDhhmmss} form and UTC timezone and \i{snapid} is a 12-character
+abbreviated commit id. As discussed below, the \c{build2} \c{version} module
+extracts and manages all this information automatically (but the use of
+\c{git} commit dates is not without limitations; see below for details).
The special '\c{z}' \i{snapsn} value identifies the \i{latest} or
\i{uncommitted} snapshot. It is chosen to be greater than any other possible
@@ -448,7 +450,7 @@ When it comes to the version control systems, the recommended workflow is as
follows: The change to the final version should be the last commit in the
(pre-)release. It is also a good idea to tag this commit with the project
version. A commit immediately after that should change the version to a
-snapshot essentially \"opening\" the repository for development.
+snapshot, \"opening\" the repository for development.
The project version without the snapshot part can be represented as a 64-bit
decimal value comparable as integers (for example, in preprocessor
@@ -517,6 +519,7 @@ as the following build system variables (which can be used in the buildfiles):
[uint64] version.snapshot_sn # 1234567
[string] version.snapshot_id # deadbeef
[string] version.snapshot_string # 1234567.deadbeef
+[bool] version.snapshot_committed # true
[uint64] version.revision # 3
\
@@ -537,11 +540,17 @@ control system used by the project. Currently only \c{git} is supported with
the following semantics.
If the project's source directory (\c{src_root}) is clean (that is, it does
-not have any changed or untracked files), then the \c{HEAD} commit date and
-id are used as the snapshot sn and id, respectively. Otherwise, the snapshot
-is left in the \c{.z} form (which signals the latest/uncommitted
-snapshot). While we can work with such a \c{.z} snapshot locally, preparing a
-distribution of such an uncommitted snapshot is an error.
+not have any changed or untracked files), then the \c{HEAD} commit date and id
+are used as the snapshot number and id, respectively.
+
+Otherwise (that is, the project is between commits), the \c{HEAD} commit date
+is incremented by one second and is used as the snapshot number with no id.
+While we can work with such uncommitted snapshots locally, we should not
+distribute or publish them since they are indistinguishable from each other.
+
+Finally, if the project does not have \c{HEAD} (that is, the project has
+no commits yet), the special \c{19700101000000} (UNIX epoch) commit date is
+used.
The use of \c{git} commit dates for snapshot ordering has its limitations:
they have one second resolution which means it is possible to create two
@@ -555,9 +564,10 @@ option, nevertheless).
When we prepare a distribution of a snapshot, the \c{version} module
automatically adjusts the package name to include the snapshot information as
-well as patches the manifest file in the distribution with the snapshot sn and
-id (that is, replacing \c{.z} in the version value with the actual snapshot
-information). The result is a package that is specific to this commit.
+well as patches the manifest file in the distribution with the snapshot number
+and id (that is, replacing \c{.z} in the version value with the actual
+snapshot information). The result is a package that is specific to this
+commit.
Besides extracting the version information and making it available as
individual components, the \c{version} module also provide rules for