aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes19
-rw-r--r--AUTHORS7
-rw-r--r--CONTRIBUTING.md16
-rw-r--r--INSTALL6
-rw-r--r--LICENSE21
-rw-r--r--NEWS1
-rw-r--r--README20
-rw-r--r--bdep-util/.gitignore3
-rw-r--r--bdep-util/buildfile38
-rw-r--r--bdep-util/git-hooks/.gitignore1
-rw-r--r--bdep-util/git-hooks/pre-commit.in23
-rw-r--r--bdep-util/git-pre-commit-copyright-check.in73
-rw-r--r--bdep-util/git-pre-commit-version-check.in220
-rw-r--r--bdep-util/git-pre-commit.in17
-rw-r--r--build/.gitignore3
-rw-r--r--build/bootstrap.build10
-rw-r--r--build/export.build19
-rw-r--r--build/root.build6
-rw-r--r--buildfile9
-rw-r--r--manifest21
-rw-r--r--repositories.manifest6
-rw-r--r--tests/.gitignore2
-rw-r--r--tests/build/.gitignore3
-rw-r--r--tests/build/bootstrap.build8
-rw-r--r--tests/build/root.build12
-rw-r--r--tests/buildfile24
-rw-r--r--tests/git-common.testscript17
-rw-r--r--tests/git-pre-commit-common.testscript13
-rw-r--r--tests/git-pre-commit-copyright-check.testscript25
-rw-r--r--tests/git-pre-commit-version-check.testscript263
-rw-r--r--tests/git-pre-commit.testscript42
31 files changed, 948 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1631641
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,19 @@
+# This is a good default: files that are auto-detected by git to be text are
+# converted to the platform-native line ending (LF on Unix, CRLF on Windows)
+# in the working tree and to LF in the repository.
+#
+* text=auto
+
+# Use `eol=crlf` for files that should have the CRLF line ending both in the
+# working tree (even on Unix) and in the repository.
+#
+#*.bat text eol=crlf
+
+# Use `eol=lf` for files that should have the LF line ending both in the
+# working tree (even on Windows) and in the repository.
+#
+#*.sh text eol=lf
+
+# Use `binary` to make sure certain files are never auto-detected as text.
+#
+#*.png binary
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..9780708
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,7 @@
+This file contains information about the build2 authors for copyright
+purposes.
+
+The copyright for the code is held by the contributors of the code. The
+revision history in the version control system is the primary source of
+authorship information for copyright purposes. Contributors that have
+requested to also be noted explicitly in this file are listed below:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..6bfc34f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,16 @@
+This project is part of the `build2` toolchain; see its
+[Community](https://build2.org/community.xhtml) page for various ways to
+contribute.
+
+The copyright for the code is held by the contributors of the code (see the
+`AUTHORS` file). The code is licensed under permissive open source licensing
+terms (see the `LICENSE` file). When you contribute code to this project, you
+license it under these terms. Before contributing please make sure that these
+terms are acceptable to you (and to your employer(s), if they have rights to
+intellectual property that you create) and that the code being contributed is
+your original creation.
+
+The revision history in the version control system is the primary source of
+authorship information for copyright purposes. If, however, you would like
+to also be noted explicitly, please include the appropriate change to the
+`AUTHORS` file along with your contribution.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..79e0d75
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,6 @@
+The easiest way to build this package is with the bpkg package manager:
+
+$ bpkg build bdep-util
+
+But if you don't want to use the package manager, then you can also build it
+manually using the standard build2 build system.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2cf3738
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2014-2020 the build2 authors (see the AUTHORS file).
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..926155a
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+See https://git.build2.org/cgit/bdep-util/log/ for the change log.
diff --git a/README b/README
new file mode 100644
index 0000000..005fd92
--- /dev/null
+++ b/README
@@ -0,0 +1,20 @@
+This package contains extra build2 project management utilities.
+
+build2 is an open source, cross-platform toolchain for building and packaging
+C++ code. Its aim is a modern build system and dependency manager for the C++
+language that provide a consistent, out of the box interface across multiple
+platforms and compilers. For more information see:
+
+https://build2.org/
+
+See the NEWS file for the user-visible changes from the previous release.
+
+See the LICENSE file for the distribution conditions.
+
+See the INSTALL file for the prerequisites and installation instructions.
+
+See the doc/ directory for documentation.
+
+Send questions, bug reports, or any other feedback to the users@build2.org
+mailing list. You can post without subscribing. See https://lists.build2.org
+for searchable archives, posting guidelines, etc.
diff --git a/bdep-util/.gitignore b/bdep-util/.gitignore
new file mode 100644
index 0000000..7ecc5ca
--- /dev/null
+++ b/bdep-util/.gitignore
@@ -0,0 +1,3 @@
+bdep-git-pre-commit
+bdep-git-pre-commit-version-check
+bdep-git-pre-commit-copyright-check
diff --git a/bdep-util/buildfile b/bdep-util/buildfile
new file mode 100644
index 0000000..5fc25c8
--- /dev/null
+++ b/bdep-util/buildfile
@@ -0,0 +1,38 @@
+# file : bdep-util/buildfile
+# license : MIT; see accompanying LICENSE file
+
+import mods = libbutl.bash%bash{manifest-parser}
+import mods += libbutl.bash%bash{standard-version}
+
+# @@ TMP Note that git-hooks/pre-commit, bdep-git-pre-commit, and
+# bdep-git-pre-commit-copyright-check have no dependencies on any bash
+# module. However, we add such dependencies for the bash module rule to
+# match. Eventually, there will be a better way to achieve that (hints).
+#
+
+# Note that git-hooks/pre-commit script just sources bdep-git-pre-commit. The
+# reason to have both scripts is to be able to configure git's pre-commit hook
+# via the core.hooksPath configuration value (should refer, for example, to
+# the /usr/local/bin/bdep-git-hooks directory) and to import
+# bdep-git-pre-commit, for example, for testing the installed hook.
+#
+git-hooks/
+{
+ exe{pre-commit}: in{pre-commit} ../exe{bdep-git-pre-commit} $mods
+}
+
+exe{bdep-git-pre-commit}: in{git-pre-commit} \
+ exe{bdep-git-pre-commit-version-check \
+ bdep-git-pre-commit-copyright-check} \
+ $mods
+
+exe{bdep-git-pre-commit-version-check}: in{git-pre-commit-version-check} \
+ $mods
+
+exe{bdep-git-pre-commit-copyright-check}: in{git-pre-commit-copyright-check} \
+ $mods
+
+# Install the git pre-commit hook into bdep-git-hooks/ subdirectory of, say,
+# /usr/bin/.
+#
+git-hooks/exe{pre-commit}: install = bin/bdep-git-hooks/
diff --git a/bdep-util/git-hooks/.gitignore b/bdep-util/git-hooks/.gitignore
new file mode 100644
index 0000000..416634f
--- /dev/null
+++ b/bdep-util/git-hooks/.gitignore
@@ -0,0 +1 @@
+pre-commit
diff --git a/bdep-util/git-hooks/pre-commit.in b/bdep-util/git-hooks/pre-commit.in
new file mode 100644
index 0000000..3339187
--- /dev/null
+++ b/bdep-util/git-hooks/pre-commit.in
@@ -0,0 +1,23 @@
+#! /usr/bin/env bash
+
+# file : bdep-util/git-hooks/pre-commit.in
+# license : MIT; see accompanying LICENSE file
+
+# Forward the execution to the pre-commit script implementation.
+#
+# To enable the hooks globally run, for example:
+#
+# $ git config --global core.hooksPath /usr/local/bin/bdep-git-hooks
+#
+# Notes:
+#
+# - git passes no parameters to this kind of hooks.
+#
+# - git changes CWD to the git repository root directory for the hook process.
+#
+# - git command running from inside the hook sees files that should be
+# auto-staged due to the -a git-commit option or similar as already staged.
+#
+trap 'exit 1' ERR
+
+source "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/../bdep-git-pre-commit"
diff --git a/bdep-util/git-pre-commit-copyright-check.in b/bdep-util/git-pre-commit-copyright-check.in
new file mode 100644
index 0000000..2e0f982
--- /dev/null
+++ b/bdep-util/git-pre-commit-copyright-check.in
@@ -0,0 +1,73 @@
+#! /usr/bin/env bash
+
+# file : bdep-util/git-pre-commit-copyright-check.in
+# license : MIT; see accompanying LICENSE file
+
+# Check copyright notices in the COPYRIGHT and LICENSE files and issue a
+# warning if they don't include the current year.
+#
+# Specifically, look first for COPYRIGHT and then LICENSE in the root
+# directory and all subdirectories in a project.
+#
+# Then in each file look for the first line matching the:
+#
+# ^Copyright (\([cC]\))? ...
+#
+# Regex, where "..." matches various year lists/ranges (e.g., "2010", "2010,
+# 2011", "2010-2011", and their combinations; see the pattern below for
+# details). Specifically, we don't consider Copyright notices that:
+#
+# - don't start at the very beginning of a line (indented, etc)
+# - contain something other than years prior to the last year (names, etc)
+# - wrap over multiple lines (long year list, etc)
+#
+# Note also that the current year is obtained in the UTC timezone.
+#
+trap 'exit 1' ERR
+set -o errtrace # Trap in functions.
+
+function info () { echo "$*" 1>&2; }
+function error () { info "$*"; exit 1; }
+
+# Recursively collect the COPYRIGHT and LICENSE files, skipping the LICENSE
+# files in directories that contain the COPYRIGHT file.
+#
+# @@ Note that for now we assume that there are no spaces in the project file
+# and directory names.
+#
+files=()
+
+fs=($(find . \( -type f -o -type l \) -name COPYRIGHT))
+
+for f in "${fs[@]}"; do
+ files+=("$f")
+done
+
+fs=($(find . \( -type f -o -type l \) -name LICENSE))
+
+for f in "${fs[@]}"; do
+ d="$(dirname "$f")"
+
+ if [ ! -f "$d/COPYRIGHT" ]; then
+ files+=("$f")
+ fi
+done
+
+# Grep for the Copyright notice in the collected files and issue the warning
+# if it is present and is outdated.
+#
+# @@ We should probably skip the COPYRIGHT/LICENSE files whose parent
+# directories don't (recursively) contain staged files (think of projects
+# with multiple packages, bundled third-party code, etc). Note that we can
+# obtain the staged file list with the `git diff --name-only --cached`
+# command.
+#
+current_year="$(date -u +'%Y')"
+
+for f in "${files[@]}"; do
+ year="$(sed -n -re 's%^Copyright( +\([cC]\))?[ 0-9,-]*[ ,-]([0-9]{4}).*$%\2%p' "$f" | head -n 1)"
+
+ if [ -n "$year" -a "$year" != "$current_year" ]; then
+ info "WARNING: last copyright year in '${f#./}' is $year"
+ fi
+done
diff --git a/bdep-util/git-pre-commit-version-check.in b/bdep-util/git-pre-commit-version-check.in
new file mode 100644
index 0000000..20cd4b0
--- /dev/null
+++ b/bdep-util/git-pre-commit-version-check.in
@@ -0,0 +1,220 @@
+#!/usr/bin/env bash
+
+# file : bdep-util/git-pre-commit-version-check.in
+# license : MIT; see accompanying LICENSE file
+
+# Check that the changes being commited are compatible with the version state
+# of the package(s).
+#
+# Specifically, fail if there are any changes staged for the released packages
+# (the version is final or a stub) unless an appropriate version change is
+# also staged.
+#
+# To achieve this, extract and compare versions corresponding to two states:
+# the latest revision in the current branch (committed) and the potential
+# result of the forthcoming commit (staged).
+#
+trap "{ exit 1; }" ERR
+set -o errtrace # Trap ERR in functions.
+
+function info () { echo "$*" 1>&2; }
+function error () { info "$*"; exit 1; }
+
+@import libbutl/manifest-parser@
+@import libbutl/standard-version@
+
+# Note that here and below a file in the existing repository revision is
+# referred to as '<rev>:<path>' (for example 'HEAD:manifest') and in the
+# staged revision as just ':<path>', where the path is relative to the project
+# root directory (see gitrevisions(7) for details).
+
+# Return 0 if the specified file revision exists and 1 otherwise.
+#
+function file_exists () # [<rev>]:<path>
+{
+ local f="$1"
+
+ if git cat-file -e "$f" 2>/dev/null; then # Repository object exists?
+ local t
+ t="$(git cat-file -t "$f")"
+
+ if [ "$t" == "blob" ]; then
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+# Wrap libbutl manifest parsing functions to parse manifest revisions and to
+# shorten names. Assumes that the specified manifest revision exists (for
+# example, this is checked with the above file_exists() function).
+#
+function manifest_parser_start () # [<rev>]:<path>
+{
+ butl_manifest_parser_start < <(git cat-file -p "$1")
+ manifest_parser_ofd="$butl_manifest_parser_ofd"
+}
+
+function manifest_parser_finish ()
+{
+ butl_manifest_parser_finish
+}
+
+# Find packages in the repository revision saving them into the specified by
+# name associative array (needs to be declared prior to the function call)
+# mapping the package names to the version/path pairs (for example "libfoo" ->
+# "1.2.3 lib/foo"). Optionally, return only released packages.
+#
+# Note that the repository revisions can be in arbitrary states and the
+# package manifests may not be necessarily present or valid. Thus, we consider
+# a package to be present in the repository revision if its manifest
+# (potentially referred to via the packages.manifest file) exists and contains
+# a non-empty package name and the valid package version. Otherwise, for the
+# staged revision, if it looks like it should be a package but something is
+# missing, we warn.
+#
+function find_packages () # <rev> <result> [<released>]
+{
+ local rev="$1"
+ local -n r="$2"
+ local rel="$3"
+
+ # Collect the potential package directories.
+ #
+ local ds=()
+ local n v
+ if file_exists "$rev:packages.manifest"; then
+ manifest_parser_start "$rev:packages.manifest"
+
+ while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do
+ if [ "$n" == "location" ]; then
+ ds+=("${v%/}")
+ fi
+ done
+
+ manifest_parser_finish
+ else
+ ds+=(.)
+ fi
+
+ # Fill the resulting package map.
+ #
+ local d
+ for d in "${ds[@]}"; do
+ local m="$d/manifest"
+ local mr="$rev:$m"
+
+ if ! file_exists "$mr"; then
+
+ # Don't warn about absence of the root package manifest file, since this
+ # git repository may well not be a build2 package.
+ #
+ if [ -z "$rev" -a "$d" != "." ]; then
+ info "warning: package manifest file $m does not exist"
+ fi
+ continue
+ fi
+
+ local name=
+ local version=
+
+ manifest_parser_start "$mr"
+
+ while IFS=: read -ru "$manifest_parser_ofd" -d '' n v; do
+ case "$n" in
+ name) name="$v" ;;
+ version) version="$v" ;;
+ esac
+ done
+
+ manifest_parser_finish
+
+ # Check if a non-empty package name is present.
+ #
+ if [ -z "$name" ]; then
+ if [ -z "$rev" ]; then
+ info "warning: package name is missing in $m"
+ fi
+ continue
+ fi
+
+ # Check if a non-empty package version is present.
+ #
+ if [ -z "$version" ]; then
+ if [ -z "$rev" ]; then
+ info "warning: package version is missing in $m"
+ fi
+ continue
+ fi
+
+ # Check if the package version is a valid standard version.
+ #
+ if ! butl_standard_version --is-version --is-not-earliest "$version"; then
+ if [ -z "$rev" ]; then
+ info "warning: package version '$version' in $m is not a valid standard version"
+ fi
+ continue
+ fi
+
+ # Optionally, skip the unreleased version.
+ #
+ if [ ! $rel ] || butl_standard_version --is-not-snapshot "$version"; then
+ r["$name"]="$version $d"
+ fi
+ done
+}
+
+# Collect the commited released packages.
+#
+declare -A committed_packages
+find_packages 'HEAD' committed_packages true
+
+# Collect all the staged packages.
+#
+# Note that while we could bail out if there are no committed released
+# packages, we will still collect the staged packages to potentially issue
+# warnings about some of the manifest errors (empty package name, etc).
+#
+declare -A staged_packages
+find_packages '' staged_packages
+
+# Iterate through the committed released packages and fail if there is a
+# change but no version change staged for this package.
+#
+for p in "${!committed_packages[@]}"; do
+ read cv cd <<<"${committed_packages[$p]}"
+
+ # Check if this is still a package in the staged revision.
+ #
+ if [[ -v staged_packages["$p"] ]]; then
+ read sv sd <<<"${staged_packages[$p]}"
+
+ # If the package version didn't change, then check for any package changes
+ # and fail if there are any.
+ #
+ if [ "$sv" == "$cv" ]; then
+
+ # Check that the package directory didn't change.
+ #
+ # If the package is moved, then detecting its changes becomes too
+ # complicated and we don't want to miss any. Let's keep it simple and
+ # deny moving the released packages (the user can always suppress the
+ # verification with --no-verify anyway).
+ #
+ if [ "$sd" != "$cd" ]; then
+ info "error: moving released package $p $cv"
+ info " info: use --no-verify git option to suppress"
+ exit 1
+ fi
+
+ # Check if the package has some staged changes in its directory.
+ #
+ if ! git diff-index --cached --quiet HEAD -- "$sd"; then
+ info "error: changing released package $p $cv without version increment"
+ info " info: use --no-verify git option to suppress"
+ exit 1
+ fi
+ fi
+ fi
+done
diff --git a/bdep-util/git-pre-commit.in b/bdep-util/git-pre-commit.in
new file mode 100644
index 0000000..27e76f3
--- /dev/null
+++ b/bdep-util/git-pre-commit.in
@@ -0,0 +1,17 @@
+#! /usr/bin/env bash
+
+# file : bdep-util/git-pre-commit.in
+# license : MIT; see accompanying LICENSE file
+
+# Execute various pre-commit scripts in a git repository.
+#
+trap 'exit 1' ERR
+
+scr_dir="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
+
+# Run each hook checking the exit status and bailing out if unsuccessful. We
+# can just exec the last one.
+#
+"$scr_dir/bdep-git-pre-commit-version-check"
+
+exec "$scr_dir/bdep-git-pre-commit-copyright-check"
diff --git a/build/.gitignore b/build/.gitignore
new file mode 100644
index 0000000..4a730a3
--- /dev/null
+++ b/build/.gitignore
@@ -0,0 +1,3 @@
+config.build
+root/
+bootstrap/
diff --git a/build/bootstrap.build b/build/bootstrap.build
new file mode 100644
index 0000000..8416065
--- /dev/null
+++ b/build/bootstrap.build
@@ -0,0 +1,10 @@
+# file : build/bootstrap.build
+# license : MIT; see accompanying LICENSE file
+
+project = bdep-util
+
+using version
+using config
+using dist
+using test
+using install
diff --git a/build/export.build b/build/export.build
new file mode 100644
index 0000000..54f91e8
--- /dev/null
+++ b/build/export.build
@@ -0,0 +1,19 @@
+# file : build/export.build
+# license : MIT; see accompanying LICENSE file
+
+$out_root/
+{
+ include bdep-util/
+}
+
+switch $import.target
+{
+ case exe{bdep-git-pre-commit}
+ export $out_root/bdep-util/exe{bdep-git-pre-commit}
+
+ case exe{bdep-git-pre-commit-version-check}
+ export $out_root/bdep-util/exe{bdep-git-pre-commit-version-check}
+
+ case exe{bdep-git-pre-commit-copyright-check}
+ export $out_root/bdep-util/exe{bdep-git-pre-commit-copyright-check}
+}
diff --git a/build/root.build b/build/root.build
new file mode 100644
index 0000000..122be6a
--- /dev/null
+++ b/build/root.build
@@ -0,0 +1,6 @@
+# file : build/root.build
+# license : MIT; see accompanying LICENSE file
+
+# Bash.
+#
+using bash
diff --git a/buildfile b/buildfile
new file mode 100644
index 0000000..efb049e
--- /dev/null
+++ b/buildfile
@@ -0,0 +1,9 @@
+# file : buildfile
+# license : MIT; see accompanying LICENSE file
+
+./: {*/ -build/} doc{INSTALL NEWS README} legal{LICENSE AUTHORS} manifest
+
+# Don't install tests or the INSTALL file.
+#
+tests/: install = false
+doc{INSTALL}@./: install = false
diff --git a/manifest b/manifest
new file mode 100644
index 0000000..ba271a5
--- /dev/null
+++ b/manifest
@@ -0,0 +1,21 @@
+: 1
+name: bdep-util
+version: 0.14.0-a.0.z
+project: build2
+summary: extra build2 project management utilities
+license: MIT
+topics: project dependency management, build toolchain
+description-file: README
+changes-file: NEWS
+url: https://build2.org
+doc-url: https://build2.org/doc.xhtml
+src-url: https://git.build2.org/cgit/bdep-util/tree/
+email: users@build2.org
+build-warning-email: builds@build2.org
+builds: all
+builds: -windows ; Requires bash.
+builds: -macos ; Requires bash >= 4.3.
+requires: bash >= 4.3
+depends: * build2 >= 0.13.0
+depends: * bpkg >= 0.13.0
+depends: libbutl.bash [0.14.0-a.0.1 0.14.0-a.1)
diff --git a/repositories.manifest b/repositories.manifest
new file mode 100644
index 0000000..8f3604c
--- /dev/null
+++ b/repositories.manifest
@@ -0,0 +1,6 @@
+: 1
+summary: extra build2 project management utilities
+
+:
+role: prerequisite
+location: ../libbutl.bash.git##HEAD
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..35ec43f
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,2 @@
+test/
+test-*/
diff --git a/tests/build/.gitignore b/tests/build/.gitignore
new file mode 100644
index 0000000..4a730a3
--- /dev/null
+++ b/tests/build/.gitignore
@@ -0,0 +1,3 @@
+config.build
+root/
+bootstrap/
diff --git a/tests/build/bootstrap.build b/tests/build/bootstrap.build
new file mode 100644
index 0000000..c9187a6
--- /dev/null
+++ b/tests/build/bootstrap.build
@@ -0,0 +1,8 @@
+# file : tests/build/bootstrap.build
+# license : MIT; see accompanying LICENSE file
+
+project = # Unnamed subproject.
+
+using config
+using dist
+using test
diff --git a/tests/build/root.build b/tests/build/root.build
new file mode 100644
index 0000000..90257ce
--- /dev/null
+++ b/tests/build/root.build
@@ -0,0 +1,12 @@
+# file : tests/build/root.build
+# license : MIT; see accompanying LICENSE file
+
+# Setup the targets that we are testing.
+#
+import git_pre_commit = bdep-util%exe{bdep-git-pre-commit}
+
+import git_pre_commit_version_check = \
+ bdep-util%exe{bdep-git-pre-commit-version-check}
+
+import git_pre_commit_copyright_check = \
+ bdep-util%exe{bdep-git-pre-commit-copyright-check}
diff --git a/tests/buildfile b/tests/buildfile
new file mode 100644
index 0000000..fb17c95
--- /dev/null
+++ b/tests/buildfile
@@ -0,0 +1,24 @@
+# file : tests/buildfile
+# license : MIT; see accompanying LICENSE file
+
+define common: file
+common{*}: extension = testscript
+
+commons = git-common git-pre-commit-common
+
+./: testscript{* -{$commons}} common{$commons}
+
+testscript{git-pre-commit}@./: $git_pre_commit
+{
+ test = $git_pre_commit
+}
+
+testscript{git-pre-commit-version-check}@./: $git_pre_commit_version_check
+{
+ test = $git_pre_commit_version_check
+}
+
+testscript{git-pre-commit-copyright-check}@./: $git_pre_commit_copyright_check
+{
+ test = $git_pre_commit_copyright_check
+}
diff --git a/tests/git-common.testscript b/tests/git-common.testscript
new file mode 100644
index 0000000..aa90f3d
--- /dev/null
+++ b/tests/git-common.testscript
@@ -0,0 +1,17 @@
+# file : tests/git-common.testscript
+# license : MIT; see accompanying LICENSE file
+
+# Set commonly-used variables and create an empty git repository that will be
+# copied by subsequent tests and scope setup commands.
+#
+g = git >! 2>&1
+gp = $g -C prj
+ga = $gp add
+gr = $gp rm
+
++$g init prj &prj/***
+
++$gp config user.name 'Test Script'
++$gp config user.email 'testscript@example.com'
+
+clone_prj = cp --no-cleanup -r ../prj ./ &prj/***
diff --git a/tests/git-pre-commit-common.testscript b/tests/git-pre-commit-common.testscript
new file mode 100644
index 0000000..ae8b9d4
--- /dev/null
+++ b/tests/git-pre-commit-common.testscript
@@ -0,0 +1,13 @@
+# file : tests/git-pre-commit-common.testscript
+# license : MIT; see accompanying LICENSE file
+
+# Set commonly-used variables and configure git to call the hook being tested.
+# Assume that git-common.testscript is already included.
+#
+gc = $gp commit -a --allow-empty-message -m ''
+
+hooks_dir=$~/hooks
++mkdir $hooks_dir
++$gp config core.hooksPath $hooks_dir
+
++ln -s $0 $hooks_dir/pre-commit
diff --git a/tests/git-pre-commit-copyright-check.testscript b/tests/git-pre-commit-copyright-check.testscript
new file mode 100644
index 0000000..f8e51f5
--- /dev/null
+++ b/tests/git-pre-commit-copyright-check.testscript
@@ -0,0 +1,25 @@
+# file : tests/git-pre-commit-copyright-check.testscript
+# license : MIT; see accompanying LICENSE file
+
+.include git-common.testscript git-pre-commit-common.testscript
+
+: basic
+:
+{
+ $clone_prj;
+
+ cat <<EOI >=prj/LICENSE;
+ Copyright (c) 2014-2019 the build2 authors.
+ EOI
+ $ga .;
+
+ $gc 2>>EOE;
+ WARNING: last copyright year in 'LICENSE' is 2019
+ EOE
+
+ date +"%Y" | set year;
+ cat <<"EOI" >=prj/LICENSE;
+ Copyright (c) 2014-$year the build2 authors.
+ EOI
+ $gc
+}
diff --git a/tests/git-pre-commit-version-check.testscript b/tests/git-pre-commit-version-check.testscript
new file mode 100644
index 0000000..28e4efc
--- /dev/null
+++ b/tests/git-pre-commit-version-check.testscript
@@ -0,0 +1,263 @@
+# file : tests/git-pre-commit-version-check.testscript
+# license : MIT; see accompanying LICENSE file
+
+.include git-common.testscript git-pre-commit-common.testscript
+
++bash --version >&2 2>! # For troubleshooting.
+
+: multi-package-project
+:
+{
+ $clone_prj;
+
+ touch prj/TODO;
+ $ga .;
+ $gc; # Initial commit.
+
+ echo '@@' >=prj/TODO;
+ $gc; # No committed packages.manifest.
+
+ cat <<EOI >=prj/packages.manifest;
+ : 1
+ location: libfoo/
+ :
+ location: libbar/
+ :
+ location: libbaz/
+ EOI
+ $ga .;
+
+ $gc 2>>EOE; # packages.manifest is staged.
+ warning: package manifest file libfoo/manifest does not exist
+ warning: package manifest file libbar/manifest does not exist
+ warning: package manifest file libbaz/manifest does not exist
+ EOE
+
+ echo '@@ todo' >=prj/TODO;
+
+ # packages.manifest is committed but there are no package manifests.
+ #
+ $gc 2>>EOE;
+ warning: package manifest file libfoo/manifest does not exist
+ warning: package manifest file libbar/manifest does not exist
+ warning: package manifest file libbaz/manifest does not exist
+ EOE
+
+ mkdir prj/libfoo prj/libbar prj/libbaz;
+ cat <<EOI >=prj/libfoo/manifest;
+ : 1
+ name: libfoo
+ version: 1.2.3-a.1.z
+ summary: Foo
+ EOI
+ cat <<EOI >=prj/libbar/manifest;
+ : 1
+ name: libbar
+ version: 1.2.3
+ summary: Bar
+ EOI
+ cat <<EOI >=prj/libbaz/manifest;
+ : 1
+ name: libbaz
+ version: 0+1
+ summary: Baz
+ EOI
+ $ga .;
+
+ $gc; # Same as above plus the package manifests are
+ # staged.
+
+ echo '@@ TODO' >=prj/TODO;
+
+ $gc; # Packages are committed but the staged change
+ # goes outside the package directories.
+
+ touch prj/libfoo/TODO prj/libbar/TODO prj/libbaz/TODO;
+ $ga libfoo/TODO;
+
+ $gc; # Package with an open version (snapshot) is
+ # changed.
+
+ $ga libbar/TODO;
+
+ $gc 2>>EOE != 0; # Package with a final version is changed.
+ error: changing released package libbar 1.2.3 without version increment
+ info: use --no-verify git option to suppress
+ EOE
+
+ sed -i -e 's/^(version:).+$/\1 1.2.4-a.1.z/' prj/libbar/manifest;
+
+ $gc; # Open package development circle.
+
+ echo '@@' >=prj/libbar/TODO;
+
+ $gc; # Development cycle is open.
+
+ $ga libbaz/TODO;
+
+ $gc 2>>EOE != 0; # Package with a stub version is changed.
+ error: changing released package libbaz 0+1 without version increment
+ info: use --no-verify git option to suppress
+ EOE
+
+ sed -i -e 's/^(version:).+$/\1 0+2/' prj/libbaz/manifest;
+
+ $gc; # Release the stub package revision.
+
+ sed -i -e 's/^(version:).+$/\1 1.2.3/' prj/libfoo/manifest;
+
+ $gc; # Release new version.
+
+ echo '@@ todo' >=prj/libfoo/TODO;
+
+ $gc 2>>EOE != 0; # Package with a final version is changed.
+ error: changing released package libfoo 1.2.3 without version increment
+ info: use --no-verify git option to suppress
+ EOE
+
+ $gr libfoo/manifest &!prj/libfoo/manifest;
+
+ $gc 2>>EOE; # The manifest removal is staged (not a package now).
+ warning: package manifest file libfoo/manifest does not exist
+ EOE
+
+ sed -i -e 's/^(version:).+$/\1 1.2.4/' prj/libbar/manifest;
+
+ $gc 2>>EOE; # Release new version.
+ warning: package manifest file libfoo/manifest does not exist
+ EOE
+
+ echo '@@ todo' >=prj/libbar/TODO;
+
+ $gc 2>>EOE != 0; # Package with a final version is changed.
+ warning: package manifest file libfoo/manifest does not exist
+ error: changing released package libbar 1.2.4 without version increment
+ info: use --no-verify git option to suppress
+ EOE
+
+ cat <<EOI >=prj/packages.manifest;
+ : 1
+ location: libbaz/
+ EOI
+
+ $gc; # The package removal from packages.manifest is
+ # staged, so it's not a package anymore.
+
+ cat <<EOI >=prj/packages.manifest;
+ : 1
+ location: lib/baz/
+ EOI
+ mkdir prj/lib;
+ mv prj/libbaz prj/lib/baz;
+ $ga .;
+
+ $gc 2>>EOE != 0; # Package with a stub version is moved.
+ error: moving released package libbaz 0+2
+ info: use --no-verify git option to suppress
+ EOE
+
+ sed -i -e 's/^(version:).+$/\1 0+3/' prj/lib/baz/manifest;
+
+ $gc # Release the stub package revision.
+}
+
+: single-package-project
+:
+{
+ $clone_prj;
+
+ cat <<EOI >=prj/manifest;
+ : 1
+ name: foo
+ version: 1.2.3
+ summary: Foo
+ EOI
+ touch prj/TODO;
+ $ga .;
+
+ $gc 2>|; # No committed manifest.
+
+ echo '@@' >=prj/TODO;
+
+ $gc 2>>EOE != 0; # Package with a final version is changed.
+ error: changing released package foo 1.2.3 without version increment
+ info: use --no-verify git option to suppress
+ EOE
+
+ sed -i -e 's/^(version:).+$/\1 1.2.4-a.1.123/' prj/manifest;
+
+ $gc; # Open package development circle.
+
+ echo '@@ todo' >=prj/TODO;
+
+ $gc; # Development cycle is open.
+
+ sed -i -e 's/^(version:).+$/\1 1.2.4/' prj/manifest;
+
+ $gc; # Release new version.
+
+ echo '@@ TODO' >=prj/TODO;
+
+ $gc 2>>EOE != 0; # Package with a final version is changed.
+ error: changing released package foo 1.2.4 without version increment
+ info: use --no-verify git option to suppress
+ EOE
+
+ $gr manifest &!prj/manifest;
+
+ # Make sure there is no warning.
+ #
+ $gc 2>:'' # The manifest removal is staged, so it's not a
+ # package anymore.
+}
+
+: warnings
+:
+{
+ +$clone_prj
+
+ : no-name
+ :
+ {
+ $clone_prj;
+
+ cat <<EOI >=prj/manifest;
+ : 1
+ summary: Foo
+ EOI
+ $ga .;
+
+ $gc 2>"warning: package name is missing in ./manifest"
+ }
+
+ : no-version
+ :
+ {
+ $clone_prj;
+
+ cat <<EOI >=prj/manifest;
+ : 1
+ name: libfoo
+ summary: Foo
+ EOI
+ $ga .;
+
+ $gc 2>"warning: package version is missing in ./manifest"
+ }
+
+ : invalid-version
+ :
+ {
+ $clone_prj;
+
+ cat <<EOI >=prj/manifest;
+ : 1
+ name: libfoo
+ version: abc
+ summary: Foo
+ EOI
+ $ga .;
+
+ $gc 2>"warning: package version 'abc' in ./manifest is not a valid standard version"
+ }
+}
diff --git a/tests/git-pre-commit.testscript b/tests/git-pre-commit.testscript
new file mode 100644
index 0000000..8b8f140
--- /dev/null
+++ b/tests/git-pre-commit.testscript
@@ -0,0 +1,42 @@
+# file : tests/git-pre-commit.testscript
+# license : MIT; see accompanying LICENSE file
+
+.include git-common.testscript git-pre-commit-common.testscript
+
+: version-check
+:
+{
+ $clone_prj;
+
+ cat <<EOI >=prj/manifest;
+ : 1
+ name: foo
+ version: 1.2.3
+ summary: Foo
+ EOI
+ touch prj/TODO;
+ $ga .;
+ $gc; # No committed manifest.
+
+ echo '@@' >=prj/TODO;
+
+ $gc 2>>EOE != 0 # Package with a final version is changed.
+ error: changing released package foo 1.2.3 without version increment
+ info: use --no-verify git option to suppress
+ EOE
+}
+
+: copyright-check
+:
+{
+ $clone_prj;
+
+ cat <<EOI >=prj/LICENSE;
+ Copyright (c) 2014-2019 the build2 authors.
+ EOI
+ $ga .;
+
+ $gc 2>>EOE
+ WARNING: last copyright year in 'LICENSE' is 2019
+ EOE
+}