diff --git a/.fish.rc b/.fish.rc new file mode 100644 index 00000000..ad8f7f11 --- /dev/null +++ b/.fish.rc @@ -0,0 +1,18 @@ +#------------------------------------------------------------------------------ +# +# This is the `git-subrepo` initialization script. +# +# This script turns on the `git-subrepo` Git subcommand and its manpages, for +# the *Fish* shell. +# +# Just add a line like this to your `~/.config/fish/config.fish`: +# +# source /path/to/git-subrepo/.fish.rc +# +#------------------------------------------------------------------------------ + +set GIT_SUBREPO_ROOT (dirname (realpath (status --current-filename))) +set PATH $GIT_SUBREPO_ROOT/lib $PATH + +set -q MANPATH || set MANPATH '' +set MANPATH $MANPATH $GIT_SUBREPO_ROOT/man diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..330ed25c --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,16 @@ +name: pre-commit + +on: + pull_request: + push: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # For PRs from forks, checkout the merge commit to avoid branch name conflicts + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..2fc2950c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,44 @@ +name: Test + +on: + push: + branches: ["*"] + pull_request: + branches: ["*"] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + token: ${{ secrets.GITHUB_TOKEN }} + - name: Ensure we're on a proper branch + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + # For PRs, create a local branch from the HEAD + git checkout -b "pr-${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.ref }}" + elif git symbolic-ref -q HEAD >/dev/null; then + # Already on a branch, nothing to do + echo "Already on branch $(git branch --show-current)" + else + # Detached HEAD, create a branch + git checkout -b "workflow-$(date +%s)" + fi + - if: startsWith(matrix.os, 'macos') + run: brew install bash + - run: git config --global user.email "you@example.com"; + git config --global user.name "Your Name"; + git config --global init.defaultBranch "master"; + git config --global --add safe.directory "$PWD"; + git config --global --add safe.directory "$PWD.git"; + - if: startsWith(matrix.os, 'macos') + run: make test + - if: startsWith(matrix.os, 'ubuntu') + run: make CI_TEST diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..eabf2e00 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/test/tmp/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..f08ca164 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: check-illegal-windows-names + - id: check-merge-conflict + - id: trailing-whitespace + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + exclude: \.t$ +- repo: https://github.com/koalaman/shellcheck-precommit + rev: v0.10.0 + hooks: + - id: shellcheck + args: [-x] + exclude: | + (?x)^(ext/.*| + share/git-completion.bash + )$ diff --git a/.rc b/.rc index 246f7bc4..6525c601 100644 --- a/.rc +++ b/.rc @@ -1,4 +1,4 @@ -#!bash +# shellcheck shell=bash disable=2128 #------------------------------------------------------------------------------ # @@ -16,10 +16,17 @@ [[ -n ${ZSH_VERSION-} ]] && GIT_SUBREPO_ROOT=$0 || GIT_SUBREPO_ROOT=$BASH_SOURCE + [[ $GIT_SUBREPO_ROOT =~ ^/ ]] || GIT_SUBREPO_ROOT=$PWD/$GIT_SUBREPO_ROOT -export GIT_SUBREPO_ROOT=$(cd "$(dirname "$GIT_SUBREPO_ROOT")"; pwd) + +GIT_SUBREPO_ROOT=$( + cd "$(dirname "$GIT_SUBREPO_ROOT")" || return + pwd +) || return +export GIT_SUBREPO_ROOT export PATH=$GIT_SUBREPO_ROOT/lib:$PATH export MANPATH=$GIT_SUBREPO_ROOT/man:$MANPATH + source "$GIT_SUBREPO_ROOT/share/enable-completion.sh" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a590c5a3..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -dist: trusty -sudo: false - -# Want 'bash' but 'c' works: -language: c - -branches: - only: - - master - - /^release\// - -script: -- git checkout $( - git branch --contains HEAD - | cut -c3- - | grep -E '^(master$|release)' - | head -1 - ) -- GIT_COMMITTER_NAME=Bob - GIT_COMMITTER_EMAIL=bob@blob.net - GIT_AUTHOR_NAME='Bob Blob' - GIT_AUTHOR_EMAIL=bob@blob.net - PROVEOPT=-v make test diff --git a/Changes b/Changes index aee5e7c1..f0b981c9 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,92 @@ +version: 0.4.9 +date: Mon 25 Jul 2024 11:23:34 AM CST +- Revert Fix bash-completion due to not being compatible with Windows +--- +version: 0.4.8 +date: Mon 22 Jul 2024 07:40:00 AM CST +- Remove all subrepo refs with clean --force +- Allow the error() function to accept multiple arguments +- Bug fixed where clone --force could change the tracked branch +- Give a detailed error message if the parent SHA could not be found +- Use the git configuration to determine the default branch name for the init command +- Add --force to fetch command +- Fix executable settings on installed files +- Remove shebangs from library only files +- Fix bash-completion for instances that used make install +--- +version: 0.4.7 +date: Sun 7 Jul 2024 11:04:00 AM EST +- Convert testing to use docker with updated git versions +- Fix numerous places where quoting was incorrect causing path name collisions +- Fix pull-force test to deal with git not defaulting to merges on pulls after 2.33 +- Fix issue where docker tests didn't work with git 2.30 or higher because of + increased permission scrutiny. +- Update readme to say this tools is in production environments +- Update readme to use internet archive for old articles about subrepo no longer + available on their original source +--- +version: 0.4.6 +date: Fri 21 Apr 2023 10:18:34 AM EST +- Remove stale worktrees in the push command +--- +version: 0.4.5 +date: Thu 22 Sep 2022 23:03:24 PM EST +- Add --file option +- Fix git subrepo status command for subrepos that share a common prefix. Closes #534. +- Don't allow -b and --all +- Fix documentation links +- fix tests to support use of a default branch git config that is not "master" +- pass --force to git add so a user's global .gitignore does not affect tests +- Fix .rc and enable-completion.sh for zsh before 5.1 +- Better format for options +- The `fpath` variable is an array; expand correctly +--- +version: 0.4.3 +date: Sat 21 Nov 2020 03:28:43 PM EST +- Apply PR#511 to fix a 0.4.2 regression in zsh +--- +version: 0.4.2 +date: Tue Nov 17 14:10:10 CST 2020 +- Covert to GitHub Actions for testing +- Add docker support to test multiple git/bash versions together +- Require Bash 4.0+ +- Use shellcheck as linter and address issues discovered +- Discovered bug in git where @ is not a valid workspace name +- Add --force command to subrepo pull +- Now works with paths that contain spaces +- Numerous documentation fixes +- When two branches pointed to the same commit, we would + sometimes pick the wrong branch. +- ZSH completion fixes +- Allow tests to run outside a git repo +- Would not work if a different date format was set in git config +- Address delay in filter-branch on newer versions of git +- Display the git merge message on merge failure +- Allow FISH integration on MacOS. +- Add manpage support for FISH shell +--- +version: 0.4.1 +date: Thu Jan 9 17:11:21 CST 2020 +- Fix Bash version error messages and add to .rc +- Nicer YAML formatting in .travis.yml +- Wrap a long line +- Update the docs +- Force `make update` to always update docs +- Don't use XXX in perl stuff +- Add testing on MacOS +- Remove conflicting -C from install -d commands. +- Update version requirement documentation +- Correct error message in branch +- Use topo-order in subrepo branch +- Make “git subrepo clean -f ...” delete refs correctly +- Fix #410 Push empty repositories with recent git versions +- Make subrepo work when run in a worktree +- Simplify finding subrepos +- Ask git to find the .gitrepo files +- Doc: fix sentence repetition +- Fix typos +- Fixed typo +- Travis CI not checking out a branch. --- version: 0.4.0 date: Thu Nov 8 12:26:38 CET 2018 diff --git a/Intro.pod b/Intro.pod index b7316fec..656c5f32 100644 --- a/Intro.pod +++ b/Intro.pod @@ -1,7 +1,7 @@ =pod =for comment -DO NOT EDIT. This Pod was generated by Swim v0.1.41. +DO NOT EDIT. This Pod was generated by Swim v0.1.48. See http://github.com/ingydotnet/swim-pm#readme =encoding utf8 @@ -78,7 +78,7 @@ Submodules tend to receive a lot of bad press. Here's some of it: =item * L -=item * L +=item * L =item * L @@ -307,12 +307,10 @@ Good: =item * Bash code is very simple and easy to follow. -=item * Comprehensive test suite. Currently passing on travis: +=item * Comprehensive test suite. =back - - Bad: =over @@ -426,10 +424,10 @@ The resulting history is: Compare that to B. This: - $ git subrepo add abc git@github.com:user/abc master - $ git subrepo add xyz git@github.com:user/def master - $ git subrepo pull abc git@github.com:user/abc master - $ git subrepo pull xyz git@github.com:user/def master + $ git subtree add abc git@github.com:user/abc master + $ git subtree add xyz git@github.com:user/def master + $ git subtree pull abc git@github.com:user/abc master + $ git subtree pull xyz git@github.com:user/def master Produces this: diff --git a/License b/License index ff754eb7..8cf5dea0 100644 --- a/License +++ b/License @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2017 Ingy döt Net +Copyright (c) 2013-2020 Ingy döt Net Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 764bd656..77a2b948 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -# Make sure we have 'git' and it works OK: +SHELL := bash + +# Make sure we have git: ifeq ($(shell which git),) $(error 'git' is not installed on this system) endif @@ -19,6 +21,22 @@ INSTALL_LIB ?= $(DESTDIR)$(shell git --exec-path) INSTALL_EXT ?= $(INSTALL_LIB)/$(NAME).d INSTALL_MAN1 ?= $(DESTDIR)$(PREFIX)/share/man/man1 +# Docker variables: +DOCKER_TAG ?= admorgan/git-subrepo-testing:1.0 +DOCKER_IMAGE := $(DOCKER_TAG) +BASH_VERSIONS ?= 5.3 5.2 5.1 5.0 4.4 4.3 4.2 4.1 4.0 +GIT_VERSIONS := 2.51 2.48 2.40 2.30 2.29 2.25 2.23 + +# Create matrix of all bash/git combinations for comprehensive testing +BASH_GIT_COMBINATIONS := $(foreach bash,$(BASH_VERSIONS),$(foreach git,$(GIT_VERSIONS),$(bash)-$(git))) +DOCKER_TESTS := $(BASH_GIT_COMBINATIONS:%=docker-test-%) +DOCKER_BASH_TESTS := $(BASH_VERSIONS:%=docker-bash-test-%) + +prove ?= +test ?= test/ +bash ?= 5.3 +git ?= 2.51 + # Basic targets: default: help @@ -29,18 +47,60 @@ help: @echo 'install Install $(NAME)' @echo 'uninstall Uninstall $(NAME)' @echo 'env Show environment variables to set' + @echo '' + @echo 'Docker testing:' + @echo 'docker-build Build Docker image with multiple bash/git versions' + @echo 'docker-tests Test ALL bash/git combinations (comprehensive)' + @echo 'docker-bash-tests Test all bash versions with default git (faster)' + @echo 'docker-test Test specific bash/git: make docker-test bash=5.1 git=2.25' + @echo 'CI_TEST Test oldest and newest bash/git combinations for CI' + @echo '' + @echo 'Available bash versions: $(BASH_VERSIONS)' + @echo 'Available git versions: $(GIT_VERSIONS)' + @echo '' .PHONY: test test: - prove $(PROVEOPT:%=% )test/ + prove $(prove) $(test) + +test-all: test docker-tests + +CI_TEST: + @echo "Running CI tests with oldest and newest bash/git combinations" + $(call docker-make-test,$(lastword $(BASH_VERSIONS)),$(lastword $(GIT_VERSIONS))) + $(call docker-make-test,$(firstword $(BASH_VERSIONS)),$(firstword $(GIT_VERSIONS))) + +docker-test: + $(call docker-make-test,$(bash),$(git)) + +docker-build: + docker build -t $(DOCKER_TAG) test/ + +# Test all bash/git combinations (comprehensive but slow) +docker-tests: $(DOCKER_TESTS) + +# Test all bash versions with default git (faster option) +docker-bash-tests: $(DOCKER_BASH_TESTS) + + + +# Parse bash-git combination from target name +$(DOCKER_TESTS): + $(eval BASH_VER := $(shell echo "$(@:docker-test-%=%)" | cut -d'-' -f1)) + $(eval GIT_VER := $(shell echo "$(@:docker-test-%=%)" | cut -d'-' -f2)) + $(call docker-make-test,$(BASH_VER),$(GIT_VER)) + +# Test specific bash version with default git +$(DOCKER_BASH_TESTS): + $(call docker-make-test,$(@:docker-bash-test-%=%),$(git)) # Install support: install: - install -C -d -m 0755 $(INSTALL_LIB)/ + install -d -m 0755 $(INSTALL_LIB)/ install -C -m 0755 $(LIB) $(INSTALL_LIB)/ - install -C -d -m 0755 $(INSTALL_EXT)/ - install -C -m 0755 $(EXTS) $(INSTALL_EXT)/ - install -C -d -m 0755 $(INSTALL_MAN1)/ + install -d -m 0755 $(INSTALL_EXT)/ + install -C -m 0644 $(EXTS) $(INSTALL_EXT)/ + install -d -m 0755 $(INSTALL_MAN1)/ install -C -m 0644 $(MAN1)/$(NAME).1 $(INSTALL_MAN1)/ # Uninstall support: @@ -57,24 +117,45 @@ env: .PHONY: doc update: doc compgen +force: + doc: ReadMe.pod Intro.pod $(MAN1)/$(NAME).1 perl pkg/bin/generate-help-functions.pl $(DOC) > \ $(EXT)/help-functions.bash -ReadMe.pod: $(DOC) +ReadMe.pod: $(DOC) force swim --to=pod --wrap --complete $< > $@ -Intro.pod: doc/intro-to-subrepo.swim +Intro.pod: doc/intro-to-subrepo.swim force swim --to=pod --wrap --complete $< > $@ -$(MAN1)/%.1: doc/%.swim Makefile +$(MAN1)/%.1: doc/%.swim Makefile force swim --to=man --wrap $< > $@ -compgen: +compgen: force perl pkg/bin/generate-completion.pl bash $(DOC) $(LIB) > \ $(SHARE)/completion.bash perl pkg/bin/generate-completion.pl zsh $(DOC) $(LIB) > \ $(SHARE)/zsh-completion/_git-subrepo + perl pkg/bin/generate-completion.pl fish $(DOC) $(LIB) > \ + $(SHARE)/git-subrepo.fish + +clean: + rm -fr tmp test/tmp -clean purge: - rm -fr tmp +define docker-make-test + docker run --rm \ + --user $(shell id -u):$(shell id -g) \ + -v $(PWD):/git-subrepo \ + -w /git-subrepo \ + $(DOCKER_IMAGE) \ + /bin/bash -c ' \ + set -x && \ + [[ -d /bash-$(1) ]] && \ + [[ -d /git-$(2) ]] && \ + export PATH=/bash-$(1)/bin:/git-$(2)/bin:$$PATH && \ + bash --version && \ + git --version && \ + make test prove=$(prove) test=$(test) \ + ' +endef diff --git a/Meta b/Meta index 56df85b0..8f5f2845 100644 --- a/Meta +++ b/Meta @@ -1,11 +1,11 @@ =meta: 0.0.2 name: git-subrepo -version: 0.4.0 +version: 0.4.9 abstract: Git Submodule Alternative homepage: https://github.com/ingydotnet/git-subrepo#readme license: MIT -copyright: 2013-2018 +copyright: 2013-2024 author: name: Ingy döt Net @@ -16,8 +16,8 @@ author: homepage: http://ingy.net requires: - bash: 3.2.0 - git: 1.7.0 + bash: 4.0.0 + git: 2.23.0 test: cmd: make test install: make install diff --git a/ReadMe.pod b/ReadMe.pod index de035fa3..7f2e00c6 100644 --- a/ReadMe.pod +++ b/ReadMe.pod @@ -1,7 +1,7 @@ =pod =for comment -DO NOT EDIT. This Pod was generated by Swim v0.1.46. +DO NOT EDIT. This Pod was generated by Swim v0.1.48. See http://github.com/ingydotnet/swim-pm#readme =encoding utf8 @@ -10,9 +10,6 @@ See http://github.com/ingydotnet/swim-pm#readme git-subrepo - Git Submodule Alternative -=for html -git-subrepo - =head1 Synopsis git subrepo -h # Help Overview @@ -117,7 +114,7 @@ The best short answer is: The complete "Installation Instructions" can be found below. -Note: git-subrepo needs a git version (> 2.5) that supports worktree:s. +Note: git-subrepo needs a git version >= 2.23) due to changes in --is-ansestor. =head1 Commands @@ -131,7 +128,7 @@ all the same arguments. Keep reading… =over -=item C<< git subrepo clone [] [-b ] [-f] [-m ] [-e] [--method ] >> +=item C<< git subrepo clone [] [-b ] [-f] [-m ] [--file=] [-e] [--method ] >> Add a repository as a subrepo in a subdir of your repository. @@ -153,8 +150,8 @@ The C<--force> option will "reclone" (completely replace) an existing subdir. The C<--method> option will decide how the join process between branches are performed. The default option is merge. -The C command accepts the C<--branch=> C<--edit>, C<--force> and C<-- -message=> options. +The C command accepts the C<--branch=> C<--edit>, C<--file>, C<--force> +and C<--message=> options. =item C<< git subrepo init [-r ] [-b ] [--method ] >> @@ -163,15 +160,13 @@ Turn an existing subdirectory into a subrepo. If you want to expose a subdirectory of your project as a published subrepo, this command will do that. It will split out the content of a normal subdirectory into a branch and start tracking it as a subrepo. Afterwards your -original repo will look exactly the same except that there will be a C<< -/.gitrepo >> file. +original repo will look exactly the same except that there will be a C<</.gitrepo >> file. If you specify the C<--remote> (and optionally the C<--branch>) option, the values will be added to the C<< /.gitrepo >> file. The C<--remote> option is the upstream URL, and the C<--branch> option is the upstream branch to push to. These values will be needed to do a C command, -but they can be provided later on the C command (and saved to C<< -/.gitrepo >> if you also specify the C<--update> option). +but they can be provided later on the C command (and saved to C<</.gitrepo >> if you also specify the C<--update> option). Note: You will need to create the empty upstream repo and push to it on your own, using C<< git subrepo push >>. @@ -181,7 +176,7 @@ performed. The default option is merge. The C command accepts the C<--branch=> and C<--remote=> options. -=item C<< git subrepo pull |--all [-M|-R|-f] [-m ] [-e] [-b ] [-r ] [-u] >> +=item C<< git subrepo pull |--all [-M|-R|-f] [-m ] [--file=] [-e] [-b ] [-r ] [-u] >> Update the subrepo subdir with the latest upstream changes. @@ -218,10 +213,6 @@ When you pull you can assume a fast-forward strategy (default) or you can specify a C<--rebase>, C<--merge> or C<--force> strategy. The latter is the same as a C operation, using the current remote and branch. -When you pull you can assume a fast-forward strategy (default) or you can -specify a C<--rebase>, C<--merge> or C<--force> strategy. The latter is the -same as a C operation, using the current remote and branch. - Like the C command, C will squash all the changes (since the last pull or clone) into one commit. This keeps your mainline history nice and clean. You can easily see the subrepo's history with the C command: @@ -230,10 +221,10 @@ clean. You can easily see the subrepo's history with the C command: The set of commands used above are described in detail below. -The C command accepts the C<--all>, C<--branch=>, C<--edit>, C<--force>, -C<--message=>, C<--remote=> and C<--update> options. +The C command accepts the C<--all>, C<--branch=>, C<--edit>, C<--file>, +C<--force>, C<--message=>, C<--remote=> and C<--update> options. -=item C<< git subrepo push |--all [] [-r ] [-b ] [-M|-R] [-u] [-f] [-s] [-N] >> +=item C<< git subrepo push |--all [] [-m msg] [--file=] [-r ] [-b ] [-M|-R] [-u] [-f] [-s] [-N] >> Push a properly merged subrepo branch back upstream. @@ -257,11 +248,9 @@ The C<--force> option will do a force push. Force pushes are typically discouraged. Only use this option if you fully understand it. (The C<--force> option will NOT check for a proper merge. ANY branch will be force pushed!) -The C command accepts the C<--all>, C<--branch=>, C<--dry-run>, C<-- -force>, C<--merge>, C<--rebase>, C<--remote=>, C<--squash> and C<-- -update> options. +The C command accepts the C<--all>, C<--branch=>, C<--dry-run>, C<--file>, C<--force>, C<--merge>, C<--message>, C<--rebase>, C<--remote=>, C<--squash> and C<--update> options. -=item C<< git subrepo fetch |--all [-r ] [-b ] >> +=item C<< git subrepo fetch |--force --all [-r ] [-b ] >> Fetch the remote/upstream content for a subrepo. @@ -270,8 +259,7 @@ points at the same commit as C. It will also create a remote called C<< subrepo/ >>. These are temporary and you can easily remove them with the subrepo C command. -The C command accepts the C<--all>, C<--branch=> and C<-- -remote=> options. +The C command accepts the C<--force>, C<--all>, C<--branch=> and C<--remote=> options. =item C<< git subrepo branch |--all [-f] [-F] >> @@ -287,12 +275,11 @@ Use the C<--force> option to write over an existing C<< subrepo/ The C command accepts the C<--all>, C<--fetch> and C<--force> options. -=item C<< git subrepo commit [] [-m ] [-e] [-f] [-F] >> +=item C<< git subrepo commit [] [-m ] [--file=] [-e] [-f] [-F] >> Add subrepo branch to current history as a single commit. -This command is generally used after a hand-merge. You have done a C and merged (rebased) it with the upstream. This command takes the HEAD +This command is generally used after a hand-merge. You have done a C and merged (rebased) it with the upstream. This command takes the HEAD of that branch, puts its content into the subrepo subdir and adds a new commit for it to the top of your mainline history. @@ -300,13 +287,12 @@ This command requires that the upstream HEAD be in the C<< subrepo/ >> branch history. That way the same branch can push upstream. Use the C<--force> option to commit anyway. -The C command accepts the C<--edit>, C<--fetch>, C<--force> and C<-- -message=> options. +The C command accepts the C<--edit>, C<--fetch>, C<--file>, C<--force> +and C<--message=> options. =item C<< git subrepo status [|--all|--ALL] [-F] [-q|-v] >> -Get the status of a subrepo. Uses the C<--all> option by default. If the C<-- -quiet> flag is used, just print the subrepo names, one per line. +Get the status of a subrepo. Uses the C<--all> option by default. If the C<--quiet> flag is used, just print the subrepo names, one per line. The C<--verbose> option will show all the recent local and upstream commits. @@ -354,8 +340,8 @@ Example to update the C option for a subrepo: Same as C. Will launch the manpage. For the shorter usage, use C. -Use C<< git subrepo help to get help for a specific command. Use >>-- -all` to get a summary of all commands. +Use C<< git subrepo help >> to get help for a specific command. Use +C<--all> to get a summary of all commands. The C command accepts the C<--all> option. @@ -423,6 +409,10 @@ Edit the commit message before committing. Use this option to fetch the upstream commits, before running the command. +=item C<< --file= >> + +Supply your own commit message from a file + =item C<--force> (C<-f>) Use this option to force certain commands that fail in the general case. @@ -493,8 +483,7 @@ The C command exports and honors some environment variables: =item C -This is set by the C<.rc> file, if you use that method to install / enable C. It contains the path of the C repository. +This is set by the C<.rc> file, if you use that method to install / enable C. It contains the path of the C repository. =item C @@ -653,8 +642,8 @@ function is called: =head1 Status -The git-subrepo command has been in use for well over a year and seems to get -the job done. Development is still ongoing but mostly just for fixing bugs. +The git-subrepo command has been used in production and seems to get the job +done. Development is still ongoing but mostly just for fixing bugs. Trying subrepo out is simple and painless (this is not C). Nothing is permanent (if you do not push to shared remotes). ie You can always @@ -683,6 +672,75 @@ C. =back +=head1 Working with nested subrepos + +C supports adding subrepos which themselves make use of subrepos. +In fact, subrepo itself is built this way. Take a look in the C folder, +it houses 2 dependencies as subrepos, C and C. + +C itself depends on 2 more subrepos: C again, and +C. + +The structure is therefore as follows: + + git-subrepo + |- bashplus + |- test-more-bash + |- bashplus + |- test-tap-bash + +However, it's important to understand how this works. Here's the key idea: +There is B special handling for nested subrepos. + +When you clone a subrepo, B C does is download the code and +set up the subrepo file. This means that all the subrepo commands simply act +on the subrepo as a whole, treating nested subrepos like any other part of the +source code. + +Let's look at what this means in practice. Say you have a structure as +follows: + + app-foo + |- barlib + |- bazlib + +You're working on app-foo, and make some changes to bazlib. How should you +upstream these changes? + +From the perspective of app-foo, you've simply made some changes to barlib. +Whether or not those changes were themselves in a subrepo is irrelevant. So, +just like any other changes, you run + +C + +Now, if C is a library you don't maintain, your responsibility would +end here. C's maintainer would see your changes and decide what to do +with them. + +If you do maintain C, you might now want to upstream the changes all +the way into C. You could be tempted to try running something like: + +C + +but you'll soon find out that doesn't work. C doesn't know anything +about the link between C and C. + +What you should do is treat the changes to C as if they would come +from another contributor, aka: + +=over + +=item * go to a local copy of C + +=item * pull down the changes with C + +=item * you notice the changes include some work on your subrepo, time to upstream them: C + +=back + +And you're done! One final step you'll likely want to do is to go back to C and run C, because the push you just did added a +new commit. + =head1 Authors =over @@ -691,12 +749,14 @@ C. =item * Magnus Carlsson +=item * Austin Morgan + =back =head1 License and Copyright The MIT License (MIT) -Copyright (c) 2013-2018 Ingy döt Net +Copyright (c) 2013-2024 Ingy döt Net =cut diff --git a/doc/git-subrepo.swim b/doc/git-subrepo.swim index 02f379b6..dfa2bed4 100644 --- a/doc/git-subrepo.swim +++ b/doc/git-subrepo.swim @@ -3,8 +3,6 @@ git-subrepo Git Submodule Alternative - - = Synopsis git subrepo -h # Help Overview @@ -78,7 +76,7 @@ The best short answer is: The complete "Installation Instructions" can be found below. -Note: git-subrepo needs a git version (> 2.5) that supports worktree:s. +Note: git-subrepo needs a git version (> 2.7) that supports worktree:s. = Commands @@ -90,7 +88,7 @@ experienced Git users. Please note that the commands are /not/ exact equivalents, and do not take all the same arguments. Keep reading… -- `git subrepo clone [] [-b ] [-f] [-m ] [-e] [--method ]` +- `git subrepo clone [] [-b ] [-f] [-m ] [--file=] [-e] [--method ]` Add a repository as a subrepo in a subdir of your repository. @@ -112,8 +110,8 @@ the same arguments. Keep reading… The `--method` option will decide how the join process between branches are performed. The default option is merge. - The `clone` command accepts the `--branch=` `--edit`, `--force` and - `--message=` options. + The `clone` command accepts the `--branch=` `--edit`, `--file`, `--force` + and `--message=` options. - `git subrepo init [-r ] [-b ] [--method ]` @@ -128,7 +126,7 @@ the same arguments. Keep reading… If you specify the `--remote` (and optionally the `--branch`) option, the values will be added to the `/.gitrepo` file. The `--remote` option is the upstream URL, and the `--branch` option is the upstream branch to push - to. These values will be needed to do a `git subrepo push` command, but they + to. These values will be needed to do a `git subrepo push` command, but they can be provided later on the `push` command (and saved to `/.gitrepo` if you also specify the `--update` option). @@ -140,7 +138,7 @@ the same arguments. Keep reading… The `init` command accepts the `--branch=` and `--remote=` options. -- `git subrepo pull |--all [-M|-R|-f] [-m ] [-e] [-b ] [-r ] [-u]` +- `git subrepo pull |--all [-M|-R|-f] [-m ] [--file=] [-e] [-b ] [-r ] [-u]` Update the subrepo subdir with the latest upstream changes. @@ -177,10 +175,6 @@ the same arguments. Keep reading… specify a `--rebase`, `--merge` or `--force` strategy. The latter is the same as a `clone --force` operation, using the current remote and branch. - When you pull you can assume a fast-forward strategy (default) or you can - specify a `--rebase`, `--merge` or `--force` strategy. The latter is the same - as a `clone --force` operation, using the current remote and branch. - Like the `clone` command, `pull` will squash all the changes (since the last pull or clone) into one commit. This keeps your mainline history nice and clean. You can easily see the subrepo's history with the `git log` command: @@ -189,10 +183,10 @@ the same arguments. Keep reading… The set of commands used above are described in detail below. - The `pull` command accepts the `--all`, `--branch=`, `--edit`, `--force`, - `--message=`, `--remote=` and `--update` options. + The `pull` command accepts the `--all`, `--branch=`, `--edit`, `--file`, + `--force`, `--message=`, `--remote=` and `--update` options. -- `git subrepo push |--all [] [-r ] [-b ] [-M|-R] [-u] [-f] [-s] [-N]` +- `git subrepo push |--all [] [-m msg] [--file=] [-r ] [-b ] [-M|-R] [-u] [-f] [-s] [-N]` Push a properly merged subrepo branch back upstream. @@ -216,10 +210,11 @@ the same arguments. Keep reading… discouraged. Only use this option if you fully understand it. (The `--force` option will NOT check for a proper merge. ANY branch will be force pushed!) - The `push` command accepts the `--all`, `--branch=`, `--dry-run`, `--force`, - `--merge`, `--rebase`, `--remote=`, `--squash` and `--update` options. + The `push` command accepts the `--all`, `--branch=`, `--dry-run`, `--file`, + `--force`, `--merge`, `--message`, `--rebase`, `--remote=`, `--squash` and + `--update` options. -- `git subrepo fetch |--all [-r ] [-b ]` +- `git subrepo fetch |--force --all [-r ] [-b ]` Fetch the remote/upstream content for a subrepo. @@ -228,7 +223,7 @@ the same arguments. Keep reading… `subrepo/`. These are temporary and you can easily remove them with the subrepo `clean` command. - The `fetch` command accepts the `--all`, `--branch=` and `--remote=` options. + The `fetch` command accepts the `--force`, `--all`, `--branch=` and `--remote=` options. - `git subrepo branch |--all [-f] [-F]` @@ -243,7 +238,7 @@ the same arguments. Keep reading… The `branch` command accepts the `--all`, `--fetch` and `--force` options. -- `git subrepo commit [] [-m ] [-e] [-f] [-F]` +- `git subrepo commit [] [-m ] [--file=] [-e] [-f] [-F]` Add subrepo branch to current history as a single commit. @@ -256,8 +251,8 @@ the same arguments. Keep reading… branch history. That way the same branch can push upstream. Use the `--force` option to commit anyway. - The `commit` command accepts the `--edit`, `--fetch`, `--force` and - `--message=` options. + The `commit` command accepts the `--edit`, `--fetch`, `--file`, `--force` + and `--message=` options. - `git subrepo status [|--all|--ALL] [-F] [-q|-v]` @@ -310,7 +305,7 @@ the same arguments. Keep reading… Same as `git help subrepo`. Will launch the manpage. For the shorter usage, use `git subrepo -h`. - Use `git subrepo help to get help for a specific command. Use + Use `git subrepo help ` to get help for a specific command. Use `--all` to get a summary of all commands. The `help` command accepts the `--all` option. @@ -374,6 +369,10 @@ the same arguments. Keep reading… Use this option to fetch the upstream commits, before running the command. +- `--file=` + + Supply your own commit message from a file + - `--force` (`-f`) Use this option to force certain commands that fail in the general case. @@ -578,7 +577,7 @@ is called: = Status -The git-subrepo command has been in use for well over a year and seems to get +The git-subrepo command has been used in production and seems to get the job done. Development is still ongoing but mostly just for fixing bugs. Trying subrepo out is simple and painless (this is not `git submodule`). @@ -600,13 +599,81 @@ If you want to chat about the `git-subrepo` command, join `#gitcommands` on * Written in (very modern) Bash, with full test suite. Take a look. * A `.gitrepo` file never is in the top level dir (next to a `.git/` dir). += Working with nested subrepos + +`git-subrepo` supports adding subrepos which themselves make use of +subrepos. In fact, subrepo itself is built this way. Take a look in the +`ext/` folder, it houses 2 dependencies as subrepos, `test-more-bash` and +`bashplus`. + +`test-more-bash` itself depends on 2 more subrepos: `bashplus` again, and +`test-tap-bash`. + +The structure is therefore as follows: + + git-subrepo + |- bashplus + |- test-more-bash + |- bashplus + |- test-tap-bash + +However, it's important to understand how this works. Here's the key idea: +There is *no* special handling for nested subrepos. + +When you clone a subrepo, *all* `git-subrepo` does is download the code +and set up the subrepo file. +This means that all the subrepo commands simply act on the subrepo as a whole, +treating nested subrepos like any other part of the source code. + +Let's look at what this means in practice. Say you have a structure as follows: + + app-foo + |- barlib + |- bazlib + +You're working on app-foo, and make some changes to bazlib. How should you +upstream these changes? + +From the perspective of app-foo, you've simply made some changes to barlib. +Whether or not those changes were themselves in a subrepo is irrelevant. So, +just like any other changes, you run + +`git subrepo push barlib` + +Now, if `barlib` is a library you don't maintain, your responsibility would end +here. `barlib`'s maintainer would see your changes and decide what to do with +them. + +If you do maintain `barlib`, you might now want to upstream the changes all the +way into `bazlib`. You could be tempted to try running something like: + +`git subrepo push barlib/bazlib` + +but you'll soon find out that doesn't work. +`app-foo` doesn't know anything about the link between `barlib` and `bazlib`. + +What you should do is treat the changes to `barlib` as if they would come from +another contributor, aka: + +* go to a local copy of `barlib` +* pull down the changes with `git pull` +* you notice the changes include some work on your subrepo, time to upstream + them: + `git subrepo push bazlib` + +And you're done! One final step you'll likely want to do is to go back to +`app-foo` and run `git subrepo pull barlib`, because the push you just did added +a new commit. + + = Authors * Ingy döt Net * Magnus Carlsson +* Austin Morgan = License and Copyright The MIT License (MIT) -Copyright (c) 2013-2018 Ingy döt Net +Copyright (c) 2013-2024 Ingy döt Net diff --git a/doc/intro-to-subrepo.swim b/doc/intro-to-subrepo.swim index 4a25bc2b..28666315 100644 --- a/doc/intro-to-subrepo.swim +++ b/doc/intro-to-subrepo.swim @@ -61,7 +61,7 @@ article is about. Submodules tend to receive a lot of bad press. Here's some of it: * http://ayende.com/blog/4746/the-problem-with-git-submodules -* http://somethingsinistral.net/blog/git-submodules-are-probably-not-the-answer/ +* https://web.archive.org/web/20171101202911/http://somethingsinistral.net/blog/git-submodules-are-probably-not-the-answer/ * http://codingkilledthecat.wordpress.com/2012/04/28/why-your-company-shouldnt-use-git-submodules/ A quick recap of some of the good and bad things about submodules: @@ -227,9 +227,7 @@ Good: * Pushed history is kept intact. * Creates a clean historical view. (See below) * Bash code is very simple and easy to follow. -* Comprehensive test suite. Currently passing on travis: - - +* Comprehensive test suite. Bad: @@ -320,10 +318,10 @@ The resulting history is: Compare that to *subtree*. This: - $ git subrepo add abc git@github.com:user/abc master - $ git subrepo add xyz git@github.com:user/def master - $ git subrepo pull abc git@github.com:user/abc master - $ git subrepo pull xyz git@github.com:user/def master + $ git subtree add abc git@github.com:user/abc master + $ git subtree add xyz git@github.com:user/def master + $ git subtree pull abc git@github.com:user/abc master + $ git subtree pull xyz git@github.com:user/def master Produces this: diff --git a/ext/bashplus/.gitignore b/ext/bashplus/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/ext/bashplus/.gitrepo b/ext/bashplus/.gitrepo index c5177a2e..7b97d025 100644 --- a/ext/bashplus/.gitrepo +++ b/ext/bashplus/.gitrepo @@ -6,6 +6,7 @@ [subrepo] remote = git@github.com:ingydotnet/bashplus.git branch = master - commit = d9183af6f46946fabdef1dd8f37824c042a378f8 - parent = 9b8f13e94677b2680a33ad204bebadcdb1ff9081 - cmdver = 0.3.0 + commit = 030d196bf621e971e223e95e73c235e6992b85e0 + parent = 2c14be68fc5196ed1210d759421b33ef91c3e3db + cmdver = 0.4.1 + method = merge diff --git a/ext/bashplus/Changes b/ext/bashplus/Changes index 6efe9712..868e06a2 100644 --- a/ext/bashplus/Changes +++ b/ext/bashplus/Changes @@ -1,4 +1,23 @@ --- +version: 0.1.0 +date: Sat 14 Nov 2020 10:14:14 AM EST +changes: +- Add tests for version-check +- Improve version-check +- Move PATH assignment into test/setup +- Meta bashplus supports Bash 3.2 +--- +version: 0.0.9 +date: Wed 11 Nov 2020 02:19:32 PM EST +changes: +- Apply shellcheck fixes +- Modernize bash code +--- +version: 0.0.8 +date: Fri Aug 21 08:00:45 PDT 2020 +changes: +- Support paths with spaces @admorgan++ +--- version: 0.0.7 date: Sat Jan 23 16:28:59 PST 2016 changes: diff --git a/ext/bashplus/License b/ext/bashplus/License index d6cc73ad..2f33d668 100644 --- a/ext/bashplus/License +++ b/ext/bashplus/License @@ -1,6 +1,6 @@ (The MIT License) -Copyright © 2013-2016 Ingy döt Net +Copyright © 2013-2020 Ingy döt Net 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 diff --git a/ext/bashplus/Makefile b/ext/bashplus/Makefile index 6ec6d713..a9e035e3 100644 --- a/ext/bashplus/Makefile +++ b/ext/bashplus/Makefile @@ -15,6 +15,10 @@ INSTALL_DIR ?= test INSTALL_MAN1 ?= $(shell bpan env BPAN_MAN1) INSTALL_MAN3 ?= $(shell bpan env BPAN_MAN3) +DOCKER_IMAGE := ingy/bash-testing:0.0.1 +DOCKER_TESTS := 5.1 5.0 4.4 4.3 4.2 4.1 4.0 3.2 +DOCKER_TESTS := $(DOCKER_TESTS:%=docker-test-%) + default: help help: @@ -24,6 +28,13 @@ help: test: prove $(PROVEOPT:%=% )test/ +test-all: test docker-test + +docker-test: $(DOCKER_TESTS) + +$(DOCKER_TESTS): + $(call docker-make-test,$(@:docker-test-%=%)) + install: install -C -d -m 0755 $(INSTALL_LIB)/$(INSTALL_DIR)/ install -C -m 0755 $(LIB) $(INSTALL_LIB)/$(INSTALL_DIR)/ @@ -43,3 +54,17 @@ $(MAN1)/%.1: doc/%.swim $(MAN3)/%.3: doc/%.swim swim --to=man $< > $@ + +define docker-make-test + docker run -i -t --rm \ + -v $(PWD):/git-subrepo \ + -w /git-subrepo \ + $(DOCKER_IMAGE) \ + /bin/bash -c ' \ + set -x && \ + [[ -d /bash-$(1) ]] && \ + export PATH=/bash-$(1)/bin:$$PATH && \ + bash --version && \ + make test \ + ' +endef diff --git a/ext/bashplus/Meta b/ext/bashplus/Meta index c4bce38f..2431c2e5 100644 --- a/ext/bashplus/Meta +++ b/ext/bashplus/Meta @@ -1,12 +1,12 @@ =meta: 0.0.2 name: bashplus -version: 0.0.7 +version: 0.1.0 abstract: Modern Bash Programming -homepage: http://bpan.org/package/bashplus/ +homepage: https://github.com/ingydotnet/bashplus license: MIT -copyright: 2013-2016 +copyright: 2013-2020 author: name: Ingy döt Net email: ingy@ingy.net @@ -16,7 +16,7 @@ author: homepage: http://ingy.net requires: - bash: 3.2.0 + bash: 3.2 test: cmd: make test install: diff --git a/ext/bashplus/ReadMe.pod b/ext/bashplus/ReadMe.pod index bf2c421c..e9a143a7 100644 --- a/ext/bashplus/ReadMe.pod +++ b/ext/bashplus/ReadMe.pod @@ -1,7 +1,7 @@ =pod =for comment -DO NOT EDIT. This Pod was generated by Swim v0.1.41. +DO NOT EDIT. This Pod was generated by Swim v0.1.48. See http://github.com/ingydotnet/swim-pm#readme =encoding utf8 @@ -70,7 +70,7 @@ Written by Ingy döt Net =head1 Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT). diff --git a/ext/bashplus/bin/bash+ b/ext/bashplus/bin/bash+ index 5178550c..a6914602 100755 --- a/ext/bashplus/bin/bash+ +++ b/ext/bashplus/bin/bash+ @@ -2,29 +2,30 @@ #------------------------------------------------------------------------------ # Bash+ - Modern Bash Programming # -# Copyright (c) 2013-2016 Ingy döt Net +# Copyright (c) 2013-2020 Ingy döt Net #------------------------------------------------------------------------------ set -e -shopt -s compat31&>/dev/null||: +shopt -s compat31 &>/dev/null || true #------------------------------------------------------------------------------ # Determine how `bash+` was called, and do the right thing: #------------------------------------------------------------------------------ -if [ "${BASH_SOURCE[0]}" != "$0" ]; then +if [[ ${BASH_SOURCE[0]} != "$0" ]]; then # 'bash+' is being sourced: - [[ "${BASH_SOURCE[0]}" =~ /bin/bash\\+$ ]] || { + [[ ${BASH_SOURCE[0]} =~ /bin/bash\\+$ ]] || { echo "Invalid Bash+ path '${BASH_SOURCE[0]}'" 2> /dev/null exit 1 } source "${BASH_SOURCE[0]%/bin/*}"/lib/bash+.bash || return $? bash+:import "$@" return $? + else - if [ $# -eq 1 -a "$1" == --version ]; then - echo 'bash+ version 0.0.7' + if [[ $# -eq 1 ]] && [[ $1 == --version ]]; then + echo 'bash+ version 0.0.9' else - cat <<... + cat <<'...' Greetings modern Bash programmer. Welcome to Bash+! diff --git a/ext/bashplus/doc/bash+.swim b/ext/bashplus/doc/bash+.swim index 69739a19..4e4d94ef 100644 --- a/ext/bashplus/doc/bash+.swim +++ b/ext/bashplus/doc/bash+.swim @@ -56,6 +56,6 @@ Written by Ingy döt Net = Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT). diff --git a/ext/bashplus/lib/bash+.bash b/ext/bashplus/lib/bash+.bash index e33f6261..55b1377a 100644 --- a/ext/bashplus/lib/bash+.bash +++ b/ext/bashplus/lib/bash+.bash @@ -1,34 +1,64 @@ # bash+ - Modern Bash Programming # -# Copyright (c) 2013-2016 Ingy döt Net +# Copyright (c) 2013-2020 Ingy döt Net -{ - bash+:version-check() { - test $1 -ge 4 && return - test $1 -eq 3 -a $2 -ge 2 && return - echo "Bash version 3.2 or higher required for 'git hub'" >&2 - exit 1 - } - bash+:version-check "${BASH_VERSINFO[@]}" - unset -f Bash:version-check +set -e + +[[ ${BASHPLUS_VERSION-} ]] && return 0 + +BASHPLUS_VERSION=0.1.0 + +bash+:version-check() { + local cmd want got out + + IFS=' ' read -r -a cmd <<< "${1:?}" + IFS=. read -r -a want <<< "${2:?}" + : "${want[0]:=0}" + : "${want[1]:=0}" + : "${want[2]:=0}" + + if [[ ${cmd[*]} == bash ]]; then + got=("${BASH_VERSINFO[@]}") + BASHPLUS_VERSION_CHECK=${BASH_VERSION-} + else + [[ ${#cmd[*]} -gt 1 ]] || cmd+=(--version) + out=$("${cmd[@]}") || + { echo "Failed to run '${cmd[*]}'" >&2; exit 1; } + [[ $out =~ ([0-9]+\.[0-9]+(\.[0-9]+)?) ]] || + { echo "Can't determine version number from '${cmd[*]}'" >&2; exit 1; } + BASHPLUS_VERSION_CHECK=${BASH_REMATCH[1]} + IFS=. read -r -a got <<< "$BASHPLUS_VERSION_CHECK" + fi + : "${got[2]:=0}" + + (( got[0] > want[0] || (( + got[0] == want[0] && (( + got[1] > want[1] || (( + got[1] == want[1] && got[2] >= want[2] + )) )) )) )) } -set -e +bash+:version-check bash 3.2 || + { echo "The 'bashplus' library requires 'Bash 3.2+'." >&2; exit 1; } -[ -z "$BASHPLUS_VERSION" ] || return 0 +bash+:export:std() { + set -o pipefail -BASHPLUS_VERSION='0.0.7' + if bash+:version-check bash 4.4; then + set -o nounset + shopt -s inherit_errexit + fi -@() { echo "$@"; } -bash+:export:std() { @ use die warn; } + echo use die warn +} # Source a bash library call import on it: bash+:use() { - local library_name="${1:?bash+:use requires library name}"; shift - local library_path=; library_path="$(bash+:findlib $library_name)" - [[ -n $library_path ]] || { + local library_name=${1:?bash+:use requires library name}; shift + local library_path=; library_path=$(bash+:findlib "$library_name") || true + [[ $library_path ]] || bash+:die "Can't find library '$library_name'." 1 - } + source "$library_path" if bash+:can "$library_name:import"; then "$library_name:import" "$@" @@ -42,9 +72,11 @@ bash+:import() { local arg= for arg; do if [[ $arg =~ ^: ]]; then - bash+:import `bash+:export$arg` + # Word splitting required here + # shellcheck disable=2046 + bash+:import $(bash+:export"$arg") else - bash+:fcopy bash+:$arg $arg + bash+:fcopy "bash+:$arg" "$arg" fi done } @@ -53,38 +85,51 @@ bash+:import() { bash+:fcopy() { bash+:can "${1:?bash+:fcopy requires an input function name}" || bash+:die "'$1' is not a function" 2 - local func=; func=$(type "$1" 3>/dev/null | tail -n+3) - [[ -n $3 ]] && "$3" + local func + func=$(type "$1" 3>/dev/null | tail -n+3) + [[ ${3-} ]] && "$3" eval "${2:?bash+:fcopy requires an output function name}() $func" } # Find the path of a library bash+:findlib() { - local library_name=; library_name="$(tr 'A-Z' 'a-z' <<< "${1//:://}").bash" - local lib="${BASHPLUSLIB:-${BASHLIB:-$PATH}}" - library_name="${library_name//+/\\+}" - find ${lib//:/ } -name ${library_name##*/} 2>/dev/null | + local library_name + library_name=$(tr '[:upper:]' '[:lower:]' <<< "${1//:://}").bash + local lib=${BASHPLUSLIB:-${BASHLIB:-$PATH}} + library_name=${library_name//+/\\+} + IFS=':' read -r -a libs <<< "$lib" + find "${libs[@]}" -name "${library_name##*/}" 2>/dev/null | grep -E "$library_name\$" | head -n1 } bash+:die() { - local msg="${1:-Died}" - printf "${msg//\\n/$'\n'}" >&2 - local trailing_newline_re=$'\n''$' - [[ $msg =~ $trailing_newline_re ]] && exit 1 - - local c=($(caller ${DIE_STACK_LEVEL:-${2:-0}})) - (( ${#c[@]} == 2 )) && - msg=" at line %d of %s" || + local msg=${1:-Died} + msg=${msg//\\n/$'\n'} + + printf "%s" "$msg" >&2 + if [[ $msg == *$'\n' ]]; then + exit 1 + else + printf "\n" + fi + + local c + IFS=' ' read -r -a c <<< "$(caller "${DIE_STACK_LEVEL:-${2:-0}}")" + if (( ${#c[@]} == 2 )); then + msg=" at line %d of %s" + else msg=" at line %d in %s of %s" - printf "$msg\n" ${c[@]} >&2 + fi + + # shellcheck disable=2059 + printf "$msg\n" "${c[@]}" >&2 exit 1 } bash+:warn() { - local msg="${1:-Warning}" - printf "${msg//\\n/$'\n'}\n" >&2 + local msg=${1:-Warning} + printf "%s" "${msg//\\n/$'\n'}\n" >&2 } bash+:can() { diff --git a/ext/bashplus/man/man1/bash+.1 b/ext/bashplus/man/man1/bash+.1 index 91f821c7..7a9978e1 100644 --- a/ext/bashplus/man/man1/bash+.1 +++ b/ext/bashplus/man/man1/bash+.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) +.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -46,7 +46,7 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" -.\" If the F register is turned on, we'll generate index entries on stderr for +.\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. @@ -56,12 +56,12 @@ .. .nr rF 0 .if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{ -. if \nF \{ +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{ +. if !\nF==2 \{\ . nr % 0 . nr F 2 . \} @@ -70,8 +70,8 @@ .rr rF .\" ======================================================================== .\" -.IX Title "Bash+(1) 1" -.TH Bash+(1) 1 "January 2016" "Generated by Swim v0.1.41" "Modern Bash Programming" +.IX Title "STDIN 1" +.TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "Modern Bash Programming" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -129,6 +129,6 @@ If you are interested in chatting about this, \f(CW\*(C`/join #bpan\*(C'\fR on i Written by Ingy döt Net .SH "Copyright & License" .IX Header "Copyright & License" -Copyright 2013\-2016. Ingy döt Net. +Copyright 2013\-2020. Ingy döt Net. .PP The \s-1MIT\s0 License (\s-1MIT\s0). diff --git a/ext/bashplus/man/man3/bash+.3 b/ext/bashplus/man/man3/bash+.3 index 91f821c7..7a9978e1 100644 --- a/ext/bashplus/man/man3/bash+.3 +++ b/ext/bashplus/man/man3/bash+.3 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) +.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -46,7 +46,7 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" -.\" If the F register is turned on, we'll generate index entries on stderr for +.\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. @@ -56,12 +56,12 @@ .. .nr rF 0 .if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{ -. if \nF \{ +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{ +. if !\nF==2 \{\ . nr % 0 . nr F 2 . \} @@ -70,8 +70,8 @@ .rr rF .\" ======================================================================== .\" -.IX Title "Bash+(1) 1" -.TH Bash+(1) 1 "January 2016" "Generated by Swim v0.1.41" "Modern Bash Programming" +.IX Title "STDIN 1" +.TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "Modern Bash Programming" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -129,6 +129,6 @@ If you are interested in chatting about this, \f(CW\*(C`/join #bpan\*(C'\fR on i Written by Ingy döt Net .SH "Copyright & License" .IX Header "Copyright & License" -Copyright 2013\-2016. Ingy döt Net. +Copyright 2013\-2020. Ingy döt Net. .PP The \s-1MIT\s0 License (\s-1MIT\s0). diff --git a/ext/bashplus/test/base.t b/ext/bashplus/test/base.t index 26e4d7f1..2fe7691a 100644 --- a/ext/bashplus/test/base.t +++ b/ext/bashplus/test/base.t @@ -1,12 +1,11 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup -PATH=$PWD/bin:$PATH source bash+ :std -ok $? '`source bash+` works' +ok $? "'source bash+' works" -is "$BASHPLUS_VERSION" '0.0.7' 'BASHPLUS_VERSION is 0.0.7' +is "$BASHPLUS_VERSION" '0.1.0' 'BASHPLUS_VERSION is 0.1.0' done_testing 2 diff --git a/ext/bashplus/test/die.t b/ext/bashplus/test/die.t new file mode 100644 index 00000000..8b3de435 --- /dev/null +++ b/ext/bashplus/test/die.t @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source test/setup + +source bash+ :std + +got=$(die "Nope" 2>&1) || true +want="Nope + at line 7 in main of test/die.t" +is "$got" "$want" "die() msg ok" + +got=$(die "Nope\n" 2>&1) || true +want="Nope" +is "$got" "$want" "die() msg ok" + +done_testing 2 diff --git a/ext/bashplus/test/fcopy.t b/ext/bashplus/test/fcopy.t index 4e0a2d40..cd9a53d2 100644 --- a/ext/bashplus/test/fcopy.t +++ b/ext/bashplus/test/fcopy.t @@ -1,8 +1,7 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup -PATH=$PWD/bin:$PATH source bash+ foo() { diff --git a/ext/test-more-bash/ext/bashplus/test/test.bash b/ext/bashplus/test/setup similarity index 78% rename from ext/test-more-bash/ext/bashplus/test/test.bash rename to ext/bashplus/test/setup index 751ac44d..c8b138d7 100644 --- a/ext/test-more-bash/ext/bashplus/test/test.bash +++ b/ext/bashplus/test/setup @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash #------------------------------------------------------------------------------ # This is a tiny version of test-more-bash that I use here. test-more-bash uses @@ -7,22 +7,28 @@ # how nice Bash can be. #------------------------------------------------------------------------------ +set -e -o pipefail + +PATH=$PWD/bin:$PATH + +run=0 + plan() { echo "1..$1" } pass() { - let run=run+1 + (( ++run )) echo "ok $run${1:+ - $1}" } fail() { - let run=run+1 + (( ++run )) echo "not ok $run${1:+ - $1}" } is() { - if [ "$1" == "$2" ]; then + if [[ $1 == "$2" ]]; then pass "$3" else fail "$3" @@ -32,13 +38,15 @@ is() { } ok() { - (exit ${1:-$?}) && - pass "$2" || + if (exit "${1:-$?}"); then + pass "$2" + else fail "$2" + fi } like() { - if [[ "$1" =~ "$2" ]]; then + if [[ $1 =~ $2 ]]; then pass "$3" else fail "$3" @@ -48,7 +56,7 @@ like() { } unlike() { - if [[ ! "$1" =~ "$2" ]]; then + if [[ ! $1 =~ $2 ]]; then pass "$3" else fail "$3" @@ -68,3 +76,5 @@ diag() { note() { echo "# ${1//$'\n'/$'\n'# }" } + +#! vim: ft=sh sw=2: diff --git a/ext/bashplus/test/shellcheck.t b/ext/bashplus/test/shellcheck.t new file mode 100644 index 00000000..d3745c9c --- /dev/null +++ b/ext/bashplus/test/shellcheck.t @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +source test/setup + +source bash+ + +if ! command -v shellcheck >/dev/null; then + plan skip_all "The 'shellcheck' utility is not installed" +fi +if [[ ! $(shellcheck --version) =~ 0\.7\.1 ]]; then + plan skip_all "This test wants shellcheck version 0.7.1" +fi + +IFS=$'\n' read -d '' -r -a shell_files <<< "$( + find bin -type f + find lib -type f + echo test/setup + find test -name '*.t' +)" || true + +skips=( + # We want to keep these 2 here always: + SC1090 # Can't follow non-constant source. Use a directive to specify location. + SC1091 # Not following: bash+ was not specified as input (see shellcheck -x). +) + +skip=$(IFS=,; echo "${skips[*]}") + +for file in "${shell_files[@]}"; do + [[ $file == *swp ]] && continue + is "$(shellcheck -e "$skip" "$file")" "" \ + "The shell file '$file' passes shellcheck" +done + +done_testing + +# vim: set ft=sh: diff --git a/ext/bashplus/test/source-bash+-std.t b/ext/bashplus/test/source-bash+-std.t index 097eae3f..b4bb1dfc 100644 --- a/ext/bashplus/test/source-bash+-std.t +++ b/ext/bashplus/test/source-bash+-std.t @@ -1,18 +1,17 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup -PATH=$PWD/bin:$PATH source bash+ :std -ok "`bash+:can use`" 'use is imported' -ok "`bash+:can die`" 'die is imported' -ok "`bash+:can warn`" 'warn is imported' +ok "$(bash+:can use)" 'use is imported' +ok "$(bash+:can die)" 'die is imported' +ok "$(bash+:can warn)" 'warn is imported' -ok "`! bash+:can import`" 'import is not imported' -ok "`! bash+:can main`" 'main is not imported' -ok "`! bash+:can fcopy`" 'fcopy is not imported' -ok "`! bash+:can findlib`" 'findlib is not imported' -ok "`! bash+:can can`" 'can is not imported' +ok "$(! bash+:can import)" 'import is not imported' +ok "$(! bash+:can main)" 'main is not imported' +ok "$(! bash+:can fcopy)" 'fcopy is not imported' +ok "$(! bash+:can findlib)" 'findlib is not imported' +ok "$(! bash+:can can)" 'can is not imported' done_testing 8 diff --git a/ext/bashplus/test/source-bash+.t b/ext/bashplus/test/source-bash+.t index 45eb070d..f54e2916 100644 --- a/ext/bashplus/test/source-bash+.t +++ b/ext/bashplus/test/source-bash+.t @@ -1,21 +1,20 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup -PATH=$PWD/bin:$PATH source bash+ functions=( - use - import - fcopy - findlib - die - warn - can + use + import + fcopy + findlib + die + warn + can ) -for f in ${functions[@]}; do +for f in "${functions[@]}"; do is "$(type -t "bash+:$f")" function \ "bash+:$f is a function" done diff --git a/ext/bashplus/test/use.t b/ext/bashplus/test/use.t index 9dea47b9..513b8350 100644 --- a/ext/bashplus/test/use.t +++ b/ext/bashplus/test/use.t @@ -1,18 +1,21 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup -PATH=$PWD/bin:$PATH source bash+ :std can +# shellcheck disable=2034 BASHLIB=test/lib use Foo::Bar + ok $? 'use Foo::Bar - works' -ok "`can Foo::Bar:baz`" 'Function Foo::Bar:baz exists' +ok "$(can Foo::Bar:baz)" 'Function Foo::Bar:baz exists' + +# shellcheck disable=2016,2154 is "$Foo__Bar_VERSION" 1.2.3 '$Foo__Bar_VERSION == 1.2.3' -output=`use Foo::Foo Boo Booo` +output=$(use Foo::Foo Boo Booo) ok $? 'use Foo::Foo Boo Booo - works' is "$output" Boo---Booo 'Correct import called' diff --git a/ext/bashplus/test/version-check.t b/ext/bashplus/test/version-check.t new file mode 100644 index 00000000..4c07e14b --- /dev/null +++ b/ext/bashplus/test/version-check.t @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +source test/setup + +PATH=$PWD/bin:$PATH +source bash+ version-check + +t1() (echo 0.1.2) +t2() (echo 0.1) + +ok "$(version-check t1 0)" "0.1.2 >= 0" +ok "$(version-check t1 0.1)" "0.1.2 >= 0.1" +ok "$(version-check t1 0.1.1)" "0.1.2 >= 0.1.1" +ok "$(version-check t1 0.1.2)" "0.1.2 >= 0.1.2" +ok "$(! version-check t1 0.2)" "0.1.2 >= 0.2 fails" +ok "$(! version-check t1 0.1.3)" "0.1.2 >= 0.1.3 fails" + +ok "$(version-check t2 0)" "0.1 >= 0" +ok "$(version-check t2 0.1)" "0.1 >= 0.1" +ok "$(! version-check t2 0.2)" "0.1 >= 0.2 fails" +ok "$(! version-check t2 0.1.1)" "0.1 >= 0.1.1" + +done_testing 10 diff --git a/ext/test-more-bash/.gitrepo b/ext/test-more-bash/.gitrepo index d8d80abd..192758fb 100644 --- a/ext/test-more-bash/.gitrepo +++ b/ext/test-more-bash/.gitrepo @@ -6,6 +6,7 @@ [subrepo] remote = git@github.com:ingydotnet/test-more-bash.git branch = master - commit = 24a6cceabff4d62a5fd25b8945bab6f618fb0b88 - parent = 69e22c2e7f6aeadfecb8b00d57c976958da03733 - cmdver = 0.3.0 + commit = c7df24fcb0814fbb62a33d92dc3c8d526ff710f0 + parent = 914c2ae8e3b881f62ca987c1c3343df6a034d865 + cmdver = 0.4.1 + method = merge diff --git a/ext/test-more-bash/Changes b/ext/test-more-bash/Changes index 254e38fe..87440715 100644 --- a/ext/test-more-bash/Changes +++ b/ext/test-more-bash/Changes @@ -1,4 +1,15 @@ --- +version: 0.0.5 +date: Wed 11 Nov 2020 02:33:42 PM EST +changes: +- Refactor to modern bash +--- +version: 0.0.4 +date: Fri Sep 4 12:26:58 2020 -0700 +changes: +- Make up to date +- Apply a few PRs +--- version: 0.0.3 date: Sat Jan 23 16:39:20 PST 2016 changes: diff --git a/ext/test-more-bash/License b/ext/test-more-bash/License index 026b9850..9fdecd71 100644 --- a/ext/test-more-bash/License +++ b/ext/test-more-bash/License @@ -1,6 +1,6 @@ (The MIT License) -Copyright © 2013-2016. Ingy döt Net. +Copyright © 2013-2020. Ingy döt Net. 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 diff --git a/ext/test-more-bash/Makefile b/ext/test-more-bash/Makefile index 3ab51953..9a730702 100644 --- a/ext/test-more-bash/Makefile +++ b/ext/test-more-bash/Makefile @@ -2,6 +2,10 @@ NAME := test-more DOC := doc/$(NAME).swim MAN3 := man/man3 +DOCKER_IMAGE := ingy/bash-testing:0.0.1 +DOCKER_TESTS := 5.1 5.0 4.4 4.3 4.2 4.1 4.0 3.2 +DOCKER_TESTS := $(DOCKER_TESTS:%=docker-test-%) + default: help help: @@ -11,6 +15,13 @@ help: test: prove $(PROVEOPT:%=% )test/ +test-all: test docker-test + +docker-test: $(DOCKER_TESTS) + +$(DOCKER_TESTS): + $(call docker-make-test,$(@:docker-test-%=%)) + doc: ReadMe.pod $(MAN3)/$(NAME).3 ReadMe.pod: $(DOC) @@ -18,3 +29,17 @@ ReadMe.pod: $(DOC) $(MAN3)/%.3: doc/%.swim swim --to=man $< > $@ + +define docker-make-test + docker run -i -t --rm \ + -v $(PWD):/git-subrepo \ + -w /git-subrepo \ + $(DOCKER_IMAGE) \ + /bin/bash -c ' \ + set -x && \ + [[ -d /bash-$(1) ]] && \ + export PATH=/bash-$(1)/bin:$$PATH && \ + bash --version && \ + make test \ + ' +endef diff --git a/ext/test-more-bash/Meta b/ext/test-more-bash/Meta index 1e58234e..23f540f0 100644 --- a/ext/test-more-bash/Meta +++ b/ext/test-more-bash/Meta @@ -1,12 +1,12 @@ =meta: 0.0.2 name: test-more -version: 0.0.3 +version: 0.0.5 abstract: TAP Testing for Bash homepage: http://bpan.org/package/test-more/ license: MIT -copyright: 2013-2016 +copyright: 2013-2020 author: name: Ingy döt Net email: ingy@ingy.net @@ -16,9 +16,9 @@ author: homepage: http://ingy.net requires: - bash: 3.2.0 - bashplus: 0.0.7 - test-tap: 0.0.4 + bash: 4.4.0 + bashplus: 0.0.9 + test-tap: 0.0.5 test: cmd: make test install: diff --git a/ext/test-more-bash/ReadMe.pod b/ext/test-more-bash/ReadMe.pod index ee8d8dd0..40ba4bd0 100644 --- a/ext/test-more-bash/ReadMe.pod +++ b/ext/test-more-bash/ReadMe.pod @@ -1,7 +1,7 @@ =pod =for comment -DO NOT EDIT. This Pod was generated by Swim v0.1.41. +DO NOT EDIT. This Pod was generated by Swim v0.1.48. See http://github.com/ingydotnet/swim-pm#readme =encoding utf8 @@ -54,6 +54,10 @@ Write a test file like this. Maybe call it C: note "A message for stdout" + output=( $(ls) ) + expected=(README lib bin) + cmp-array output expected "list files" + Run the test with C like this: prove test/test.t @@ -98,17 +102,19 @@ This is the basic usage: =item * C +=item * `cmp-array output expected "message" + =back More detailed info coming soon. =head1 Author -Ingy döt Net +Ingy döt Net =head1 Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT) diff --git a/ext/test-more-bash/doc/test-more.swim b/ext/test-more-bash/doc/test-more.swim index c3ad0b19..7687f63d 100644 --- a/ext/test-more-bash/doc/test-more.swim +++ b/ext/test-more-bash/doc/test-more.swim @@ -46,6 +46,10 @@ Write a test file like this. Maybe call it `test/test.t`: note "A message for stdout" + output=( $(ls) ) + expected=(README lib bin) + cmp-array output expected "list files" + Run the test with `prove` like this: prove test/test.t @@ -75,15 +79,16 @@ This is the basic usage: * `done_testing $count` * `plan skip_all "$reason"` * `BAIL_OUT "$reason"` +* `cmp-array output expected "message" More detailed info coming soon. = Author -Ingy döt Net +Ingy döt Net = Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT) diff --git a/ext/test-more-bash/ext/bashplus/.gitignore b/ext/test-more-bash/ext/bashplus/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/ext/test-more-bash/ext/bashplus/.gitrepo b/ext/test-more-bash/ext/bashplus/.gitrepo index 97f817be..c56b46be 100644 --- a/ext/test-more-bash/ext/bashplus/.gitrepo +++ b/ext/test-more-bash/ext/bashplus/.gitrepo @@ -6,6 +6,7 @@ [subrepo] remote = git@github.com:ingydotnet/bashplus.git branch = master - commit = d9183af6f46946fabdef1dd8f37824c042a378f8 - parent = 05a1bddbe237bbf2c390048c80ca70d4f66c6a37 - cmdver = 0.3.0 + commit = e49f45a1457fed3cceb15bd4a82b0f7515efd8e5 + parent = c978e2afd6861203138f28d0021e03fa1ffbba0c + cmdver = 0.4.1 + method = merge diff --git a/ext/test-more-bash/ext/bashplus/Changes b/ext/test-more-bash/ext/bashplus/Changes index 6efe9712..645bd805 100644 --- a/ext/test-more-bash/ext/bashplus/Changes +++ b/ext/test-more-bash/ext/bashplus/Changes @@ -1,4 +1,15 @@ --- +version: 0.0.9 +date: Wed 11 Nov 2020 02:19:32 PM EST +changes: +- Apply shellcheck fixes +- Modernize bash code +--- +version: 0.0.8 +date: Fri Aug 21 08:00:45 PDT 2020 +changes: +- Support paths with spaces @admorgan++ +--- version: 0.0.7 date: Sat Jan 23 16:28:59 PST 2016 changes: diff --git a/ext/test-more-bash/ext/bashplus/License b/ext/test-more-bash/ext/bashplus/License index d6cc73ad..2f33d668 100644 --- a/ext/test-more-bash/ext/bashplus/License +++ b/ext/test-more-bash/ext/bashplus/License @@ -1,6 +1,6 @@ (The MIT License) -Copyright © 2013-2016 Ingy döt Net +Copyright © 2013-2020 Ingy döt Net 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 diff --git a/ext/test-more-bash/ext/bashplus/Makefile b/ext/test-more-bash/ext/bashplus/Makefile index 6ec6d713..a9e035e3 100644 --- a/ext/test-more-bash/ext/bashplus/Makefile +++ b/ext/test-more-bash/ext/bashplus/Makefile @@ -15,6 +15,10 @@ INSTALL_DIR ?= test INSTALL_MAN1 ?= $(shell bpan env BPAN_MAN1) INSTALL_MAN3 ?= $(shell bpan env BPAN_MAN3) +DOCKER_IMAGE := ingy/bash-testing:0.0.1 +DOCKER_TESTS := 5.1 5.0 4.4 4.3 4.2 4.1 4.0 3.2 +DOCKER_TESTS := $(DOCKER_TESTS:%=docker-test-%) + default: help help: @@ -24,6 +28,13 @@ help: test: prove $(PROVEOPT:%=% )test/ +test-all: test docker-test + +docker-test: $(DOCKER_TESTS) + +$(DOCKER_TESTS): + $(call docker-make-test,$(@:docker-test-%=%)) + install: install -C -d -m 0755 $(INSTALL_LIB)/$(INSTALL_DIR)/ install -C -m 0755 $(LIB) $(INSTALL_LIB)/$(INSTALL_DIR)/ @@ -43,3 +54,17 @@ $(MAN1)/%.1: doc/%.swim $(MAN3)/%.3: doc/%.swim swim --to=man $< > $@ + +define docker-make-test + docker run -i -t --rm \ + -v $(PWD):/git-subrepo \ + -w /git-subrepo \ + $(DOCKER_IMAGE) \ + /bin/bash -c ' \ + set -x && \ + [[ -d /bash-$(1) ]] && \ + export PATH=/bash-$(1)/bin:$$PATH && \ + bash --version && \ + make test \ + ' +endef diff --git a/ext/test-more-bash/ext/bashplus/Meta b/ext/test-more-bash/ext/bashplus/Meta index c4bce38f..3104879f 100644 --- a/ext/test-more-bash/ext/bashplus/Meta +++ b/ext/test-more-bash/ext/bashplus/Meta @@ -1,12 +1,12 @@ =meta: 0.0.2 name: bashplus -version: 0.0.7 +version: 0.0.9 abstract: Modern Bash Programming homepage: http://bpan.org/package/bashplus/ license: MIT -copyright: 2013-2016 +copyright: 2013-2020 author: name: Ingy döt Net email: ingy@ingy.net @@ -16,7 +16,7 @@ author: homepage: http://ingy.net requires: - bash: 3.2.0 + bash: 4.4.0 test: cmd: make test install: diff --git a/ext/test-more-bash/ext/bashplus/ReadMe.pod b/ext/test-more-bash/ext/bashplus/ReadMe.pod index bf2c421c..e9a143a7 100644 --- a/ext/test-more-bash/ext/bashplus/ReadMe.pod +++ b/ext/test-more-bash/ext/bashplus/ReadMe.pod @@ -1,7 +1,7 @@ =pod =for comment -DO NOT EDIT. This Pod was generated by Swim v0.1.41. +DO NOT EDIT. This Pod was generated by Swim v0.1.48. See http://github.com/ingydotnet/swim-pm#readme =encoding utf8 @@ -70,7 +70,7 @@ Written by Ingy döt Net =head1 Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT). diff --git a/ext/test-more-bash/ext/bashplus/bin/bash+ b/ext/test-more-bash/ext/bashplus/bin/bash+ index 5178550c..a6914602 100755 --- a/ext/test-more-bash/ext/bashplus/bin/bash+ +++ b/ext/test-more-bash/ext/bashplus/bin/bash+ @@ -2,29 +2,30 @@ #------------------------------------------------------------------------------ # Bash+ - Modern Bash Programming # -# Copyright (c) 2013-2016 Ingy döt Net +# Copyright (c) 2013-2020 Ingy döt Net #------------------------------------------------------------------------------ set -e -shopt -s compat31&>/dev/null||: +shopt -s compat31 &>/dev/null || true #------------------------------------------------------------------------------ # Determine how `bash+` was called, and do the right thing: #------------------------------------------------------------------------------ -if [ "${BASH_SOURCE[0]}" != "$0" ]; then +if [[ ${BASH_SOURCE[0]} != "$0" ]]; then # 'bash+' is being sourced: - [[ "${BASH_SOURCE[0]}" =~ /bin/bash\\+$ ]] || { + [[ ${BASH_SOURCE[0]} =~ /bin/bash\\+$ ]] || { echo "Invalid Bash+ path '${BASH_SOURCE[0]}'" 2> /dev/null exit 1 } source "${BASH_SOURCE[0]%/bin/*}"/lib/bash+.bash || return $? bash+:import "$@" return $? + else - if [ $# -eq 1 -a "$1" == --version ]; then - echo 'bash+ version 0.0.7' + if [[ $# -eq 1 ]] && [[ $1 == --version ]]; then + echo 'bash+ version 0.0.9' else - cat <<... + cat <<'...' Greetings modern Bash programmer. Welcome to Bash+! diff --git a/ext/test-more-bash/ext/bashplus/doc/bash+.swim b/ext/test-more-bash/ext/bashplus/doc/bash+.swim index 69739a19..4e4d94ef 100644 --- a/ext/test-more-bash/ext/bashplus/doc/bash+.swim +++ b/ext/test-more-bash/ext/bashplus/doc/bash+.swim @@ -56,6 +56,6 @@ Written by Ingy döt Net = Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT). diff --git a/ext/test-more-bash/ext/bashplus/lib/bash+.bash b/ext/test-more-bash/ext/bashplus/lib/bash+.bash index e33f6261..2e1a70c2 100644 --- a/ext/test-more-bash/ext/bashplus/lib/bash+.bash +++ b/ext/test-more-bash/ext/bashplus/lib/bash+.bash @@ -1,34 +1,66 @@ # bash+ - Modern Bash Programming # -# Copyright (c) 2013-2016 Ingy döt Net +# Copyright (c) 2013-2020 Ingy döt Net -{ - bash+:version-check() { - test $1 -ge 4 && return - test $1 -eq 3 -a $2 -ge 2 && return - echo "Bash version 3.2 or higher required for 'git hub'" >&2 - exit 1 - } - bash+:version-check "${BASH_VERSINFO[@]}" - unset -f Bash:version-check +set -e + +[[ ${BASHPLUS_VERSION-} ]] && return 0 + +BASHPLUS_VERSION=0.0.9 + +bash+:version-check() { + local cmd want got out + + IFS=' ' read -r -a cmd <<< "${1:?}" + IFS=. read -r -a want <<< "${2:?}" + : "${want[2]:=0}" + + if [[ ${cmd[*]} == bash ]]; then + got=("${BASH_VERSINFO[@]}") + BASHPLUS_VERSION_CHECK=${BASH_VERSION-} + else + [[ ${#cmd[*]} -gt 1 ]] || cmd+=(--version) + out=$("${cmd[@]}") || + { echo "Failed to run '${cmd[*]}'" >&2; exit 1; } + [[ $out =~ ([0-9]+\.[0-9]+(\.[0-9]+)?) ]] || + { echo "Can't determine version number from '${cmd[*]}'" >&2; exit 1; } + BASHPLUS_VERSION_CHECK=${BASH_REMATCH[1]} + IFS=. read -r -a got <<< "$BASHPLUS_VERSION_CHECK" + fi + : "${got[2]:=0}" + + (( + got[0] > want[0] || + got[0] == want[0] && got[1] > want[1] || + got[0] == want[0] && got[1] == want[1] && got[2] >= want[2] + )) || return 1 + + return 0 } -set -e +bash+:version-check bash 3.2 || + { echo "The 'bashplus' library requires 'Bash 3.2+'." >&2; exit 1; } -[ -z "$BASHPLUS_VERSION" ] || return 0 +@() (echo "$@") # XXX do we want to keep this? -BASHPLUS_VERSION='0.0.7' +bash+:export:std() { + set -o pipefail -@() { echo "$@"; } -bash+:export:std() { @ use die warn; } + if bash+:version-check bash 4.4; then + set -o nounset + shopt -s inherit_errexit + fi + + echo use die warn +} # Source a bash library call import on it: bash+:use() { - local library_name="${1:?bash+:use requires library name}"; shift - local library_path=; library_path="$(bash+:findlib $library_name)" - [[ -n $library_path ]] || { + local library_name=${1:?bash+:use requires library name}; shift + local library_path=; library_path=$(bash+:findlib "$library_name") || true + [[ $library_path ]] || bash+:die "Can't find library '$library_name'." 1 - } + source "$library_path" if bash+:can "$library_name:import"; then "$library_name:import" "$@" @@ -42,9 +74,11 @@ bash+:import() { local arg= for arg; do if [[ $arg =~ ^: ]]; then - bash+:import `bash+:export$arg` + # Word splitting required here + # shellcheck disable=2046 + bash+:import $(bash+:export"$arg") else - bash+:fcopy bash+:$arg $arg + bash+:fcopy "bash+:$arg" "$arg" fi done } @@ -53,38 +87,51 @@ bash+:import() { bash+:fcopy() { bash+:can "${1:?bash+:fcopy requires an input function name}" || bash+:die "'$1' is not a function" 2 - local func=; func=$(type "$1" 3>/dev/null | tail -n+3) - [[ -n $3 ]] && "$3" + local func + func=$(type "$1" 3>/dev/null | tail -n+3) + [[ ${3-} ]] && "$3" eval "${2:?bash+:fcopy requires an output function name}() $func" } # Find the path of a library bash+:findlib() { - local library_name=; library_name="$(tr 'A-Z' 'a-z' <<< "${1//:://}").bash" - local lib="${BASHPLUSLIB:-${BASHLIB:-$PATH}}" - library_name="${library_name//+/\\+}" - find ${lib//:/ } -name ${library_name##*/} 2>/dev/null | + local library_name + library_name=$(tr '[:upper:]' '[:lower:]' <<< "${1//:://}").bash + local lib=${BASHPLUSLIB:-${BASHLIB:-$PATH}} + library_name=${library_name//+/\\+} + IFS=':' read -r -a libs <<< "$lib" + find "${libs[@]}" -name "${library_name##*/}" 2>/dev/null | grep -E "$library_name\$" | head -n1 } bash+:die() { - local msg="${1:-Died}" - printf "${msg//\\n/$'\n'}" >&2 - local trailing_newline_re=$'\n''$' - [[ $msg =~ $trailing_newline_re ]] && exit 1 - - local c=($(caller ${DIE_STACK_LEVEL:-${2:-0}})) - (( ${#c[@]} == 2 )) && - msg=" at line %d of %s" || + local msg=${1:-Died} + msg=${msg//\\n/$'\n'} + + printf "%s" "$msg" >&2 + if [[ $msg == *$'\n' ]]; then + exit 1 + else + printf "\n" + fi + + local c + IFS=' ' read -r -a c <<< "$(caller "${DIE_STACK_LEVEL:-${2:-0}}")" + if (( ${#c[@]} == 2 )); then + msg=" at line %d of %s" + else msg=" at line %d in %s of %s" - printf "$msg\n" ${c[@]} >&2 + fi + + # shellcheck disable=2059 + printf "$msg\n" "${c[@]}" >&2 exit 1 } bash+:warn() { - local msg="${1:-Warning}" - printf "${msg//\\n/$'\n'}\n" >&2 + local msg=${1:-Warning} + printf "%s" "${msg//\\n/$'\n'}\n" >&2 } bash+:can() { diff --git a/ext/test-more-bash/ext/bashplus/man/man1/bash+.1 b/ext/test-more-bash/ext/bashplus/man/man1/bash+.1 index 91f821c7..7a9978e1 100644 --- a/ext/test-more-bash/ext/bashplus/man/man1/bash+.1 +++ b/ext/test-more-bash/ext/bashplus/man/man1/bash+.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) +.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -46,7 +46,7 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" -.\" If the F register is turned on, we'll generate index entries on stderr for +.\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. @@ -56,12 +56,12 @@ .. .nr rF 0 .if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{ -. if \nF \{ +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{ +. if !\nF==2 \{\ . nr % 0 . nr F 2 . \} @@ -70,8 +70,8 @@ .rr rF .\" ======================================================================== .\" -.IX Title "Bash+(1) 1" -.TH Bash+(1) 1 "January 2016" "Generated by Swim v0.1.41" "Modern Bash Programming" +.IX Title "STDIN 1" +.TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "Modern Bash Programming" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -129,6 +129,6 @@ If you are interested in chatting about this, \f(CW\*(C`/join #bpan\*(C'\fR on i Written by Ingy döt Net .SH "Copyright & License" .IX Header "Copyright & License" -Copyright 2013\-2016. Ingy döt Net. +Copyright 2013\-2020. Ingy döt Net. .PP The \s-1MIT\s0 License (\s-1MIT\s0). diff --git a/ext/test-more-bash/ext/bashplus/man/man3/bash+.3 b/ext/test-more-bash/ext/bashplus/man/man3/bash+.3 index 91f821c7..7a9978e1 100644 --- a/ext/test-more-bash/ext/bashplus/man/man3/bash+.3 +++ b/ext/test-more-bash/ext/bashplus/man/man3/bash+.3 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) +.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -46,7 +46,7 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" -.\" If the F register is turned on, we'll generate index entries on stderr for +.\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. @@ -56,12 +56,12 @@ .. .nr rF 0 .if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{ -. if \nF \{ +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{ +. if !\nF==2 \{\ . nr % 0 . nr F 2 . \} @@ -70,8 +70,8 @@ .rr rF .\" ======================================================================== .\" -.IX Title "Bash+(1) 1" -.TH Bash+(1) 1 "January 2016" "Generated by Swim v0.1.41" "Modern Bash Programming" +.IX Title "STDIN 1" +.TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "Modern Bash Programming" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -129,6 +129,6 @@ If you are interested in chatting about this, \f(CW\*(C`/join #bpan\*(C'\fR on i Written by Ingy döt Net .SH "Copyright & License" .IX Header "Copyright & License" -Copyright 2013\-2016. Ingy döt Net. +Copyright 2013\-2020. Ingy döt Net. .PP The \s-1MIT\s0 License (\s-1MIT\s0). diff --git a/ext/test-more-bash/ext/bashplus/test/base.t b/ext/test-more-bash/ext/bashplus/test/base.t index 26e4d7f1..35d5e505 100644 --- a/ext/test-more-bash/ext/bashplus/test/base.t +++ b/ext/test-more-bash/ext/bashplus/test/base.t @@ -1,12 +1,12 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup PATH=$PWD/bin:$PATH source bash+ :std -ok $? '`source bash+` works' +ok $? "'source bash+' works" -is "$BASHPLUS_VERSION" '0.0.7' 'BASHPLUS_VERSION is 0.0.7' +is "$BASHPLUS_VERSION" '0.0.9' 'BASHPLUS_VERSION is 0.0.9' done_testing 2 diff --git a/ext/test-more-bash/ext/bashplus/test/die.t b/ext/test-more-bash/ext/bashplus/test/die.t new file mode 100644 index 00000000..42dabf8e --- /dev/null +++ b/ext/test-more-bash/ext/bashplus/test/die.t @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +source test/setup + +PATH=$PWD/bin:$PATH +source bash+ :std + +got=$(die "Nope" 2>&1) || true +want="Nope + at line 8 in main of test/die.t" +is "$got" "$want" "die() msg ok" + +got=$(die "Nope\n" 2>&1) || true +want="Nope" +is "$got" "$want" "die() msg ok" + +done_testing 2 diff --git a/ext/test-more-bash/ext/bashplus/test/fcopy.t b/ext/test-more-bash/ext/bashplus/test/fcopy.t index 4e0a2d40..47ef6ec1 100644 --- a/ext/test-more-bash/ext/bashplus/test/fcopy.t +++ b/ext/test-more-bash/ext/bashplus/test/fcopy.t @@ -1,6 +1,6 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup PATH=$PWD/bin:$PATH source bash+ diff --git a/ext/bashplus/test/test.bash b/ext/test-more-bash/ext/bashplus/test/setup similarity index 77% rename from ext/bashplus/test/test.bash rename to ext/test-more-bash/ext/bashplus/test/setup index 751ac44d..35124d0f 100644 --- a/ext/bashplus/test/test.bash +++ b/ext/test-more-bash/ext/bashplus/test/setup @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash #------------------------------------------------------------------------------ # This is a tiny version of test-more-bash that I use here. test-more-bash uses @@ -7,22 +7,27 @@ # how nice Bash can be. #------------------------------------------------------------------------------ +set -e -u -o pipefail +[[ $BASH_VERSION == 4.0* ]] && set +u + +run=0 + plan() { echo "1..$1" } pass() { - let run=run+1 + (( ++run )) echo "ok $run${1:+ - $1}" } fail() { - let run=run+1 + (( ++run )) echo "not ok $run${1:+ - $1}" } is() { - if [ "$1" == "$2" ]; then + if [[ $1 == "$2" ]]; then pass "$3" else fail "$3" @@ -32,13 +37,15 @@ is() { } ok() { - (exit ${1:-$?}) && - pass "$2" || + if (exit "${1:-$?}"); then + pass "$2" + else fail "$2" + fi } like() { - if [[ "$1" =~ "$2" ]]; then + if [[ $1 =~ $2 ]]; then pass "$3" else fail "$3" @@ -48,7 +55,7 @@ like() { } unlike() { - if [[ ! "$1" =~ "$2" ]]; then + if [[ ! $1 =~ $2 ]]; then pass "$3" else fail "$3" @@ -68,3 +75,5 @@ diag() { note() { echo "# ${1//$'\n'/$'\n'# }" } + +#! vim: ft=sh sw=2: diff --git a/ext/test-more-bash/ext/bashplus/test/shellcheck.t b/ext/test-more-bash/ext/bashplus/test/shellcheck.t new file mode 100644 index 00000000..fcc0c97a --- /dev/null +++ b/ext/test-more-bash/ext/bashplus/test/shellcheck.t @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +source test/setup + +PATH=$PWD/bin:$PATH +source bash+ + +if ! command -v shellcheck >/dev/null; then + plan skip_all "The 'shellcheck' utility is not installed" +fi +if [[ ! $(shellcheck --version) =~ 0\.7\.1 ]]; then + plan skip_all "This test wants shellcheck version 0.7.1" +fi + +IFS=$'\n' read -d '' -r -a shell_files <<< "$( + find bin -type f + find lib -type f + echo test/setup + find test -name '*.t' +)" || true + +skips=( + # We want to keep these 2 here always: + SC1090 # Can't follow non-constant source. Use a directive to specify location. + SC1091 # Not following: bash+ was not specified as input (see shellcheck -x). +) + +skip=$(IFS=,; echo "${skips[*]}") + +for file in "${shell_files[@]}"; do + [[ $file == *swp ]] && continue + is "$(shellcheck -e "$skip" "$file")" "" \ + "The shell file '$file' passes shellcheck" +done + +done_testing + +# vim: set ft=sh: diff --git a/ext/test-more-bash/ext/bashplus/test/source-bash+-std.t b/ext/test-more-bash/ext/bashplus/test/source-bash+-std.t index 097eae3f..71574f4f 100644 --- a/ext/test-more-bash/ext/bashplus/test/source-bash+-std.t +++ b/ext/test-more-bash/ext/bashplus/test/source-bash+-std.t @@ -1,18 +1,18 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup PATH=$PWD/bin:$PATH source bash+ :std -ok "`bash+:can use`" 'use is imported' -ok "`bash+:can die`" 'die is imported' -ok "`bash+:can warn`" 'warn is imported' +ok "$(bash+:can use)" 'use is imported' +ok "$(bash+:can die)" 'die is imported' +ok "$(bash+:can warn)" 'warn is imported' -ok "`! bash+:can import`" 'import is not imported' -ok "`! bash+:can main`" 'main is not imported' -ok "`! bash+:can fcopy`" 'fcopy is not imported' -ok "`! bash+:can findlib`" 'findlib is not imported' -ok "`! bash+:can can`" 'can is not imported' +ok "$(! bash+:can import)" 'import is not imported' +ok "$(! bash+:can main)" 'main is not imported' +ok "$(! bash+:can fcopy)" 'fcopy is not imported' +ok "$(! bash+:can findlib)" 'findlib is not imported' +ok "$(! bash+:can can)" 'can is not imported' done_testing 8 diff --git a/ext/test-more-bash/ext/bashplus/test/source-bash+.t b/ext/test-more-bash/ext/bashplus/test/source-bash+.t index 45eb070d..539b8bfb 100644 --- a/ext/test-more-bash/ext/bashplus/test/source-bash+.t +++ b/ext/test-more-bash/ext/bashplus/test/source-bash+.t @@ -1,21 +1,21 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup PATH=$PWD/bin:$PATH source bash+ functions=( - use - import - fcopy - findlib - die - warn - can + use + import + fcopy + findlib + die + warn + can ) -for f in ${functions[@]}; do +for f in "${functions[@]}"; do is "$(type -t "bash+:$f")" function \ "bash+:$f is a function" done diff --git a/ext/test-more-bash/ext/bashplus/test/use.t b/ext/test-more-bash/ext/bashplus/test/use.t index 9dea47b9..e9aafcb8 100644 --- a/ext/test-more-bash/ext/bashplus/test/use.t +++ b/ext/test-more-bash/ext/bashplus/test/use.t @@ -1,18 +1,22 @@ -#!/bin/bash -e +#!/usr/bin/env bash -source test/test.bash +source test/setup PATH=$PWD/bin:$PATH source bash+ :std can +# shellcheck disable=2034 BASHLIB=test/lib use Foo::Bar + ok $? 'use Foo::Bar - works' -ok "`can Foo::Bar:baz`" 'Function Foo::Bar:baz exists' +ok "$(can Foo::Bar:baz)" 'Function Foo::Bar:baz exists' + +# shellcheck disable=2016,2154 is "$Foo__Bar_VERSION" 1.2.3 '$Foo__Bar_VERSION == 1.2.3' -output=`use Foo::Foo Boo Booo` +output=$(use Foo::Foo Boo Booo) ok $? 'use Foo::Foo Boo Booo - works' is "$output" Boo---Booo 'Correct import called' diff --git a/ext/test-more-bash/ext/test-tap-bash/.gitrepo b/ext/test-more-bash/ext/test-tap-bash/.gitrepo index 92d0eddc..a6b1a06a 100644 --- a/ext/test-more-bash/ext/test-tap-bash/.gitrepo +++ b/ext/test-more-bash/ext/test-tap-bash/.gitrepo @@ -6,6 +6,7 @@ [subrepo] remote = git@github.com:ingydotnet/test-tap-bash.git branch = master - commit = 7890df93f13e684715750a2d6a608e7e79671ca4 - parent = 3faca582ee96a320b5675ad67b47ad0f16730446 - cmdver = 0.3.0 + commit = a9b33446848440a445f664a942f676d726788eff + parent = 24ba0cb0c97d2591cfc2c106a70b1d1f8c559e01 + cmdver = 0.4.1 + method = merge diff --git a/ext/test-more-bash/ext/test-tap-bash/.travis.yml b/ext/test-more-bash/ext/test-tap-bash/.travis.yml index 8c7bcad5..5d433e92 100644 --- a/ext/test-more-bash/ext/test-tap-bash/.travis.yml +++ b/ext/test-more-bash/ext/test-tap-bash/.travis.yml @@ -2,5 +2,4 @@ language: c script: -- git submodule update --init --recursive -- PROVEOPT=-v make test +- make test PROVEOPT=-v diff --git a/ext/test-more-bash/ext/test-tap-bash/Changes b/ext/test-more-bash/ext/test-tap-bash/Changes index 1fdc3e7f..01b07c6c 100644 --- a/ext/test-more-bash/ext/test-tap-bash/Changes +++ b/ext/test-more-bash/ext/test-tap-bash/Changes @@ -1,4 +1,15 @@ --- +version: 0.0.6 +date: Tue 03 Nov 2020 05:20:54 PM EST +changes: +- Workaround for bash 4.0 bug +- Docker testing for all bash versions +--- +version: 0.0.5 +date: Tue 03 Nov 2020 01:15:41 PM EST +changes: +- Use more modern Bash idioms +--- version: 0.0.4 date: Sat Jan 23 16:32:22 PST 2016 changes: diff --git a/ext/test-more-bash/ext/test-tap-bash/License b/ext/test-more-bash/ext/test-tap-bash/License index 026b9850..9fdecd71 100644 --- a/ext/test-more-bash/ext/test-tap-bash/License +++ b/ext/test-more-bash/ext/test-tap-bash/License @@ -1,6 +1,6 @@ (The MIT License) -Copyright © 2013-2016. Ingy döt Net. +Copyright © 2013-2020. Ingy döt Net. 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 diff --git a/ext/test-more-bash/ext/test-tap-bash/Makefile b/ext/test-more-bash/ext/test-tap-bash/Makefile index 5c6a4ea6..7e75c236 100644 --- a/ext/test-more-bash/ext/test-tap-bash/Makefile +++ b/ext/test-more-bash/ext/test-tap-bash/Makefile @@ -1,3 +1,5 @@ +SHELL := bash + ifeq ($(MAKECMDGOALS),install) ifeq "$(shell bpan version 2>/dev/null)" "" $(error 'BPAN not installed. See http://bpan.org') @@ -12,6 +14,8 @@ INSTALL_LIB ?= $(shell bpan env BPAN_LIB) INSTALL_DIR ?= test INSTALL_MAN3 ?= $(shell bpan env BPAN_MAN3) +DOCKER_IMAGE := ingy/bash-testing:0.0.1 + default: help help: @@ -21,6 +25,18 @@ help: test: prove $(PROVEOPT:%=% )test/ +test-all: test docker-test + +docker-test: + -$(call docker-make-test,3.2) + -$(call docker-make-test,4.0) + -$(call docker-make-test,4.1) + -$(call docker-make-test,4.2) + -$(call docker-make-test,4.3) + -$(call docker-make-test,4.4) + -$(call docker-make-test,5.0) + -$(call docker-make-test,5.1) + install: install -C -d -m 0755 $(INSTALL_LIB)/$(INSTALL_DIR)/ install -C -m 0755 $(LIB) $(INSTALL_LIB)/$(INSTALL_DIR)/ @@ -35,3 +51,17 @@ ReadMe.pod: doc/test-tap.swim man/man3/%.3: doc/%.swim swim --to=man $< > $@ + +define docker-make-test + docker run -i -t --rm \ + -v $(PWD):/git-subrepo \ + -w /git-subrepo \ + $(DOCKER_IMAGE) \ + /bin/bash -c ' \ + set -x && \ + [[ -d /bash-$(1) ]] && \ + export PATH=/bash-$(1)/bin:$$PATH && \ + bash --version && \ + make test \ + ' +endef diff --git a/ext/test-more-bash/ext/test-tap-bash/Meta b/ext/test-more-bash/ext/test-tap-bash/Meta index fb212b5c..db06a608 100644 --- a/ext/test-more-bash/ext/test-tap-bash/Meta +++ b/ext/test-more-bash/ext/test-tap-bash/Meta @@ -1,12 +1,12 @@ =meta: 0.0.2 name: test-tap -version: 0.0.4 +version: 0.0.6 abstract: TAP Test Base for Bash homepage: http://bpan.org/package/test-tap/ license: MIT -copyright: 2013-2016 +copyright: 2013-2020 author: name: Ingy döt Net email: ingy@ingy.net @@ -16,7 +16,7 @@ author: homepage: http://ingy.net requires: - bash: 3.2.0 + bash: 3.2.57 test: cmd: make test install: diff --git a/ext/test-more-bash/ext/test-tap-bash/ReadMe.pod b/ext/test-more-bash/ext/test-tap-bash/ReadMe.pod index 45f277a4..aced80a8 100644 --- a/ext/test-more-bash/ext/test-tap-bash/ReadMe.pod +++ b/ext/test-more-bash/ext/test-tap-bash/ReadMe.pod @@ -1,7 +1,7 @@ =pod =for comment -DO NOT EDIT. This Pod was generated by Swim v0.1.41. +DO NOT EDIT. This Pod was generated by Swim v0.1.48. See http://github.com/ingydotnet/swim-pm#readme =encoding utf8 @@ -59,7 +59,7 @@ Written by Ingy döt Net =head1 Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT). diff --git a/ext/test-more-bash/ext/test-tap-bash/doc/test-tap.swim b/ext/test-more-bash/ext/test-tap-bash/doc/test-tap.swim index 4ec3f1fb..a27c18ee 100644 --- a/ext/test-more-bash/ext/test-tap-bash/doc/test-tap.swim +++ b/ext/test-more-bash/ext/test-tap-bash/doc/test-tap.swim @@ -43,6 +43,6 @@ Written by Ingy döt Net = Copyright & License -Copyright 2013-2016. Ingy döt Net. +Copyright 2013-2020. Ingy döt Net. The MIT License (MIT). diff --git a/ext/test-more-bash/ext/test-tap-bash/lib/test/tap.bash b/ext/test-more-bash/ext/test-tap-bash/lib/test/tap.bash index 276a595c..cc34dcb9 100644 --- a/ext/test-more-bash/ext/test-tap-bash/lib/test/tap.bash +++ b/ext/test-more-bash/ext/test-tap-bash/lib/test/tap.bash @@ -1,22 +1,26 @@ # test/tap.bash - TAP Testing Foundation for Bash # -# Copyright (c) 2013-2016. Ingy döt Net. +# Copyright (c) 2013-2020. Ingy döt Net. #------------------------------------------------------------------------------ Test::Tap:die() { echo "$@" >&2; trap EXIT; exit 1; } #------------------------------------------------------------------------------ -Test__Tap_VERSION=0.0.4 +set -e -u -o pipefail +[[ ${BASH_VERSION-} == 4.0* ]] && set +u + +# shellcheck disable=2034 +Test__Tap_VERSION=0.0.6 Test::Tap:init() { - [ -n "$BASH_SOURCE" ] || + [[ ${BASH_SOURCE[0]} ]] || Test::Tap:die "Error: test-tap-bash must be run under Bash only" Test__Tap_plan=0 Test__Tap_run=0 Test__Tap_failed=0 - Test__Tap_pid=$BASHPID + Test__Tap_pid=${BASHPID:-0} - if [ $# -gt 0 ]; then + if [[ $# -gt 0 ]]; then [[ $# -eq 2 ]] || Test::Tap:die 'Usage: test/tap.bash tests ' Test::Tap:plan "$@" @@ -27,17 +31,17 @@ Test::Tap:init() { Test::Tap:plan() { Test::Tap:_check-pid - [ $# -eq 2 ] || + [[ $# -eq 2 ]] || Test::Tap:die 'Usage: plan tests ' - if [ "$1" = tests ]; then - [[ "$2" =~ ^-?[0-9]+$ ]] || + if [[ $1 = tests ]]; then + [[ $2 =~ ^-?[0-9]+$ ]] || Test::Tap:die 'Plan must be a number' [[ $2 -gt 0 ]] || Test::Tap:die 'Plan must greater then 0' Test__Tap_plan=$2 - printf "1..%d\n" $Test__Tap_plan - elif [ "$1" == skip_all ]; then - printf "1..0 # SKIP $2\n" + printf "1..%d\n" "$Test__Tap_plan" + elif [[ $1 == skip_all ]]; then + printf "1..0 # SKIP %s\n" "$2" exit 0 else Test::Tap:die 'Usage: plan tests ' @@ -46,9 +50,9 @@ Test::Tap:plan() { Test::Tap:pass() { Test::Tap:_check-pid - let Test__Tap_run=Test__Tap_run+1 - local label="$1" - if [ -n "$label" ]; then + ((++Test__Tap_run)) + local label=${1-} + if [[ $label ]]; then echo "ok $Test__Tap_run - $label" else echo "ok $Test__Tap_run" @@ -58,12 +62,12 @@ Test::Tap:pass() { Test__Tap_CALL_STACK_LEVEL=1 Test::Tap:fail() { Test::Tap:_check-pid - let Test__Tap_run=Test__Tap_run+1 - local c=( $(caller $Test__Tap_CALL_STACK_LEVEL) ) - local file=${c[2]} - local line=${c[0]} - local label="$1" callback="$2" - if [ -n "$label" ]; then + ((++Test__Tap_run)) + IFS=' ' read -r -a c <<<"$(caller $Test__Tap_CALL_STACK_LEVEL)" + local file=${c[2]-} + local line=${c[0]-} + local label=${1-} callback=${2-} + if [[ $label ]]; then echo "not ok $Test__Tap_run - $label" else echo "not ok $Test__Tap_run" @@ -72,35 +76,35 @@ Test::Tap:fail() { label=${label:-"at $file line $line."} echo -e "# Failed test $label" >&2 - [ -n "$callback" ] && $callback + [[ $callback ]] && $callback local rc=${TEST_TAP_BAIL_ON_FAIL:-0} - [[ $TEST_TAP_BAIL_ON_FAIL -eq 0 ]] || exit $rc + [[ $rc -eq 0 ]] || exit "$rc" } Test::Tap:done_testing() { Test::Tap:_check-pid Test__Tap_plan=$Test__Tap_run - echo 1..${1:-$Test__Tap_run} + echo 1.."${1:-$Test__Tap_run}" } Test::Tap:diag() { Test::Tap:_check-pid - local msg="$@" + local msg=$* msg="# ${msg//$'\n'/$'\n'# }" echo "$msg" >&2 } Test::Tap:note() { Test::Tap:_check-pid - local msg="$@" + local msg=$* msg="# ${msg//$'\n'/$'\n'# }" echo "$msg" } Test::Tap:BAIL_OUT() { Test::Tap:_check-pid - Test__Tap_bail_msg="$@" + Test__Tap_bail_msg=$* : "${Test__Tap_bail_msg:=No reason given.}" exit 255 } @@ -113,41 +117,41 @@ Test::Tap:BAIL_ON_FAIL() { Test::Tap:END() { local rc=$? Test::Tap:_check-pid - if [ $rc -ne 0 ]; then - if [ -n "$Test__Tap_bail_msg" -o -n "$TEST_TAP_BAIL_ON_FAIL" ]; then - local bail="${Test__Tap_bail_msg:-"Bailing out on status=$rc"}" + if [[ $rc -ne 0 ]]; then + if [[ ${Test__Tap_bail_msg-} ]] || + [[ ${TEST_TAP_BAIL_ON_FAIL-} ]]; then + local bail=${Test__Tap_bail_msg:-"Bailing out on status=$rc"} echo "Bail out! $bail" exit $rc fi fi - for v in plan run failed; do eval local $v=\$Test__Tap_$v; done - if [ $plan -eq 0 ]; then - if [ $run -gt 0 ]; then + if [[ $Test__Tap_plan -eq 0 ]]; then + if [[ $Test__Tap_run -gt 0 ]]; then echo "# Tests were run but no plan was declared." >&2 fi else - if [ $run -eq 0 ]; then + if [[ $Test__Tap_run -eq 0 ]]; then echo "# No tests run!" >&2 - elif [ $run -ne $plan ]; then - local msg="# Looks like you planned $plan tests but ran $run." - [ $plan -eq 1 ] && msg=${msg/tests/test} + elif [[ $Test__Tap_run -ne $Test__Tap_plan ]]; then + local msg="# Looks like you planned $Test__Tap_plan tests but ran $Test__Tap_run." + [[ $Test__Tap_plan -eq 1 ]] && msg=${msg/tests/test} echo "$msg" >&2 fi fi local exit_code=0 - if [ $Test__Tap_failed -gt 0 ]; then + if [[ $Test__Tap_failed -gt 0 ]]; then exit_code=$Test__Tap_failed - [ $exit_code -gt 254 ] && exit_code=254 - local msg="# Looks like you failed $failed tests of $run run." - [ $Test__Tap_failed -eq 1 ] && msg=${msg/tests/test} + [[ $exit_code -gt 254 ]] && exit_code=254 + local msg="# Looks like you failed $Test__Tap_failed tests of $Test__Tap_run run." + [[ $Test__Tap_failed -eq 1 ]] && msg=${msg/tests/test} echo "$msg" >&2 fi exit $exit_code } Test::Tap:_check-pid() { - if [ ${BASHPID:-0} -ne ${Test__Tap_pid:-0} ]; then - die "Error: Called Test::Tap method from a subprocess" 3 + if [[ ${BASHPID:-0} -ne ${Test__Tap_pid:-0} ]]; then + Test::Tap:die "Error: Called Test::Tap method from a subprocess" 3 fi } diff --git a/ext/test-more-bash/ext/test-tap-bash/man/man3/test-tap.3 b/ext/test-more-bash/ext/test-tap-bash/man/man3/test-tap.3 index 9a08fe68..595fc87f 100644 --- a/ext/test-more-bash/ext/test-tap-bash/man/man3/test-tap.3 +++ b/ext/test-more-bash/ext/test-tap-bash/man/man3/test-tap.3 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) +.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -46,7 +46,7 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" -.\" If the F register is turned on, we'll generate index entries on stderr for +.\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. @@ -56,12 +56,12 @@ .. .nr rF 0 .if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{ -. if \nF \{ +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{ +. if !\nF==2 \{\ . nr % 0 . nr F 2 . \} @@ -70,8 +70,8 @@ .rr rF .\" ======================================================================== .\" -.IX Title "Test::Tap 1" -.TH Test::Tap 1 "January 2016" "Generated by Swim v0.1.41" "\s-1TAP\s0 Test Base for Bash" +.IX Title "STDIN 1" +.TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "\s-1TAP\s0 Test Base for Bash" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -114,6 +114,6 @@ Finish this doc. Written by Ingy döt Net .SH "Copyright & License" .IX Header "Copyright & License" -Copyright 2013\-2016. Ingy döt Net. +Copyright 2013\-2020. Ingy döt Net. .PP The \s-1MIT\s0 License (\s-1MIT\s0). diff --git a/ext/test-more-bash/ext/test-tap-bash/test/bail_out.t b/ext/test-more-bash/ext/test-tap-bash/test/bail_out.t index 20ed4e02..d73617c2 100644 --- a/ext/test-more-bash/ext/test-tap-bash/test/bail_out.t +++ b/ext/test-more-bash/ext/test-tap-bash/test/bail_out.t @@ -5,7 +5,7 @@ source lib/test/tap.bash Test::Tap:init tests 1 -output=$(prove -v test/test/{b,f}ail.t 2>&1) +output=$(prove -v test/test/{b,f}ail.t 2>&1) || true test-helper:like \ "$output" \ diff --git a/ext/test-more-bash/ext/test-tap-bash/test/fail.t b/ext/test-more-bash/ext/test-tap-bash/test/fail.t index fc0b9243..364853b1 100644 --- a/ext/test-more-bash/ext/test-tap-bash/test/fail.t +++ b/ext/test-more-bash/ext/test-tap-bash/test/fail.t @@ -5,7 +5,7 @@ source lib/test/tap.bash Test::Tap:init tests 2 -output="$(prove -v test/test/fail.t 2>&1)" +output=$(prove -v test/test/fail.t 2>&1) || true # echo "# >>>${output//$'\n'/$'\n'# }<<<" >&2 diff --git a/ext/test-more-bash/ext/test-tap-bash/test/fail_fast.t b/ext/test-more-bash/ext/test-tap-bash/test/fail_fast.t index 18679bf5..3e6fb05e 100644 --- a/ext/test-more-bash/ext/test-tap-bash/test/fail_fast.t +++ b/ext/test-more-bash/ext/test-tap-bash/test/fail_fast.t @@ -5,7 +5,7 @@ source lib/test/tap.bash Test::Tap:init tests 1 -output="$(prove -v test/test/fail_fast.t 2>&1)" +output=$(prove -v test/test/fail_fast.t 2>&1) || true # echo ">>>$output<<<" >&2 diff --git a/ext/test-more-bash/ext/test-tap-bash/test/helper.bash b/ext/test-more-bash/ext/test-tap-bash/test/helper.bash index 2762b27b..f272ee3f 100644 --- a/ext/test-more-bash/ext/test-tap-bash/test/helper.bash +++ b/ext/test-more-bash/ext/test-tap-bash/test/helper.bash @@ -1,6 +1,6 @@ test-helper:like() { local got=$1 regex=$2 label=$3 - if [[ "$got" =~ "$regex" ]]; then + if [[ $got =~ $regex ]]; then Test::Tap:pass "$label" else Test::Tap:fail "$label" diff --git a/ext/test-more-bash/ext/test-tap-bash/test/shellcheck.t b/ext/test-more-bash/ext/test-tap-bash/test/shellcheck.t new file mode 100644 index 00000000..3efda399 --- /dev/null +++ b/ext/test-more-bash/ext/test-tap-bash/test/shellcheck.t @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +source lib/test/tap.bash + +if ! command -v shellcheck >/dev/null; then + Test::Tap:init skip_all "The 'shellcheck' utility is not installed" +fi +if [[ ! $(shellcheck --version) =~ 0\.7\.1 ]]; then + Test::Tap:init skip_all "This test wants shellcheck version 0.7.1" +fi + +Test::Tap:init + +IFS=$'\n' read -d '' -r -a shell_files <<< "$( + find lib -type f + echo test/helper.bash + find test -name '*.t' +)" || true + +skips=( + # We want to keep these 2 here always: + SC1090 # Can't follow non-constant source. Use a directive to specify location. + SC1091 # Not following: bash+ was not specified as input (see shellcheck -x). +) + +skip=$(IFS=,; echo "${skips[*]}") + +for file in "${shell_files[@]}"; do + [[ $file == *swp ]] && continue + label="The shell file '$file' passes shellcheck" + got=$(shellcheck -e "$skip" "$file" 2>&1) || true + if [[ -z $got ]]; then + Test::Tap:pass "$label" + else + Test::Tap:fail "$label" + Test::Tap:diag "$got" + fi +done + +Test::Tap:done_testing + +# vim: set ft=sh: diff --git a/ext/test-more-bash/ext/test-tap-bash/test/tap.t b/ext/test-more-bash/ext/test-tap-bash/test/tap.t index 88bcd189..610937fd 100644 --- a/ext/test-more-bash/ext/test-tap-bash/test/tap.t +++ b/ext/test-more-bash/ext/test-tap-bash/test/tap.t @@ -2,12 +2,18 @@ source lib/test/tap.bash -Test::Tap:init tests 4 +Test::Tap:init tests 3 # 4 Test::Tap:pass 'pass with label' Test::Tap:pass Test::Tap:pass 'previous test has no label' -msg="$(Test::Tap:fail 'faaaaailll' 2>/dev/null)" -if [[ "$msg" =~ not\ ok\ 4\ -\ faaaaailll ]]; then - Test::Tap:pass 'fail works' -fi + +# TODO this test no longer working: +# msg=$(Test::Tap:fail 'faaaaailll' 2>/dev/null) || true +# +# if [[ $msg =~ not\ ok\ 4\ -\ faaaaailll ]]; then +# Test::Tap:pass 'fail works' +# else +# Test::Tap:fail 'fail works' +# Test::Tap:diag "$msg" +# fi diff --git a/ext/test-more-bash/lib/test/more.bash b/ext/test-more-bash/lib/test/more.bash index 84d1fdb1..6fa9f56d 100644 --- a/ext/test-more-bash/lib/test/more.bash +++ b/ext/test-more-bash/lib/test/more.bash @@ -1,12 +1,17 @@ # test/more.bash - Complete TAP test framework for Bash # -# Copyright (c) 2013-2016. Ingy döt Net. +# Copyright (c) 2013-2020. Ingy döt Net. -set -e +set -e -u -o pipefail -Test__More_VERSION=0.0.3 +# shellcheck disable=2034 +Test__More_VERSION=0.0.5 + +source bash+ :std version-check + +version-check bash 3.2 || + die "test-more-bash requires bash 3.2+" -source bash+ :std use Test::Tap Test::More:import() { Test::Tap:init "$@"; } @@ -21,7 +26,7 @@ BAIL_OUT() { Test::Tap:BAIL_OUT "$@"; } BAIL_ON_FAIL() { Test::Tap:BAIL_ON_FAIL "$@"; } is() { - local got="$1" want="$2" label="$3" + local got=$1 want=$2 label=${3-} if [[ $got == "$want" ]]; then Test::Tap:pass "$label" else @@ -32,10 +37,10 @@ is() { Test::More:is-fail() { local Test__Tap_CALL_STACK_LEVEL= Test__Tap_CALL_STACK_LEVEL=$(( Test__Tap_CALL_STACK_LEVEL + 1 )) - if [[ "$want" =~ \n ]]; then + if [[ $want =~ $'\n' ]]; then echo "$got" > /tmp/got-$$ echo "$want" > /tmp/want-$$ - diff -u /tmp/{want,got}-$$ >&2 + diff -u /tmp/{want,got}-$$ >&2 || true wc /tmp/{want,got}-$$ >&2 rm -f /tmp/{got,want}-$$ else @@ -48,7 +53,7 @@ Test::More:is-fail() { isnt() { local Test__Tap_CALL_STACK_LEVEL= Test__Tap_CALL_STACK_LEVEL=$(( Test__Tap_CALL_STACK_LEVEL + 1 )) - local got="$1" dontwant="$2" label="$3" + local got=$1 dontwant=$2 label=${3-} if [[ $got != "$dontwant" ]]; then Test::Tap:pass "$label" else @@ -63,14 +68,16 @@ Test::More:isnt-fail() { } ok() { - (exit ${1:-$?}) && - Test::Tap:pass "$2" || - Test::Tap:fail "$2" + if (exit "${1:-$?}"); then + Test::Tap:pass "${2-}" + else + Test::Tap:fail "${2-}" + fi } like() { - local got=$1 regex=$2 label=$3 - if [[ $got =~ "$regex" ]]; then + local got=$1 regex=$2 label=${3-} + if [[ $got =~ $regex ]]; then Test::Tap:pass "$label" else Test::Tap:fail "$label" Test::More:like-fail @@ -82,8 +89,8 @@ Test::More:like-fail() { } unlike() { - local got=$1 regex=$2 label=$3 - if [[ ! $got =~ "$regex" ]]; then + local got=$1 regex=$2 label=${3-} + if [[ ! $got =~ $regex ]]; then Test::Tap:pass "$label" else Test::Tap:fail "$label" Test::More:unlike-fail @@ -93,3 +100,16 @@ unlike() { Test::More:unlike-fail() { Test::Tap:diag "Got: '$got'" } + +cmp-array() { + local arrayname="$1[@]" + local expname="$2[@]" + local label=${3-} + + local array=("${!arrayname}") + local expected=("${!expname}") + + is "$(printf "%s\n" "${array[@]}")" \ + "$(printf "%s\n" "${expected[@]}")" \ + "$label" +} diff --git a/ext/test-more-bash/man/man3/test-more.3 b/ext/test-more-bash/man/man3/test-more.3 index c72f1cbd..b548d961 100644 --- a/ext/test-more-bash/man/man3/test-more.3 +++ b/ext/test-more-bash/man/man3/test-more.3 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28) +.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== @@ -46,7 +46,7 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" -.\" If the F register is turned on, we'll generate index entries on stderr for +.\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. @@ -56,12 +56,12 @@ .. .nr rF 0 .if \n(.g .if rF .nr rF 1 -.if (\n(rF:(\n(.g==0)) \{ -. if \nF \{ +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{ +. if !\nF==2 \{\ . nr % 0 . nr F 2 . \} @@ -70,8 +70,8 @@ .rr rF .\" ======================================================================== .\" -.IX Title "Test::More 1" -.TH Test::More 1 "January 2016" "Generated by Swim v0.1.41" "\s-1TAP\s0 Testing for Bash" +.IX Title "STDIN 1" +.TH STDIN 1 "November 2020" "Generated by Swim v0.1.48" "\s-1TAP\s0 Testing for Bash" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -120,6 +120,10 @@ Write a test file like this. Maybe call it \f(CW\*(C`test/test.t\*(C'\fR: \& diag "A message for stderr" \& \& note "A message for stdout" +\& +\& output=( $(ls) ) +\& expected=(README lib bin) +\& cmp\-array output expected "list files" .Ve .PP Run the test with \f(CW\*(C`prove\*(C'\fR like this: @@ -131,7 +135,7 @@ Run the test with \f(CW\*(C`prove\*(C'\fR like this: Prove knows it's Bash from the first line (the hashbang), and it just works. .SH "Description" .IX Header "Description" -Test::More is the tried and true testing library for Perl. It uses \s-1TAP \s0(the Test Anything Protocol). This is the same thing for Bash. For the most part it should work exactly the same. +Test::More is the tried and true testing library for Perl. It uses \s-1TAP\s0 (the Test Anything Protocol). This is the same thing for Bash. For the most part it should work exactly the same. .SH "Methods" .IX Header "Methods" This is the basic usage: @@ -161,13 +165,15 @@ This is the basic usage: \&\f(CW\*(C`plan skip_all "$reason"\*(C'\fR .IP "\(bu" 4 \&\f(CW\*(C`BAIL_OUT "$reason"\*(C'\fR +.IP "\(bu" 4 +`cmp\-array output expected \*(L"message\*(R" .PP More detailed info coming soon. .SH "Author" .IX Header "Author" -Ingy döt Net +Ingy döt Net .SH "Copyright & License" .IX Header "Copyright & License" -Copyright 2013\-2016. Ingy döt Net. +Copyright 2013\-2020. Ingy döt Net. .PP The \s-1MIT\s0 License (\s-1MIT\s0) diff --git a/ext/test-more-bash/test/fail.t b/ext/test-more-bash/test/fail.t index 2d5ac30f..4d8ea796 100644 --- a/ext/test-more-bash/test/fail.t +++ b/ext/test-more-bash/test/fail.t @@ -12,9 +12,23 @@ like "$output" 'not ok 2' \ 'fail with no label' like "$output" 'not ok 3 - is foo bar' \ 'fail output is correct' +like "$output" 'not ok 4 - command output more' \ + 'fail output is correct' +like "$output" 'not ok 5 - command output less' \ + 'fail output is correct' +like "$output" 'not ok 6 - command output diff' \ + 'fail output is correct' like "$output" "# got: 'foo'" \ 'difference reporting - got' like "$output" "# expected: 'bar'" \ 'difference reporting - want' -done_testing 5 +like "$output" "line2. *\+line3." \ + 'array comparison (more)' +like "$output" "line1. *-line2." \ + 'array comparison (less)' +like "$output" "-line2.*\+foo" \ + 'array comparison (diff)' + + +done_testing 11 diff --git a/ext/test-more-bash/test/more.t b/ext/test-more-bash/test/more.t index 95878b71..27528380 100644 --- a/ext/test-more-bash/test/more.t +++ b/ext/test-more-bash/test/more.t @@ -1,20 +1,28 @@ #!/usr/bin/env bash +# shellcheck disable=2034 + source test/setup use Test::More -plan tests 5 +plan tests 6 pass 'This test always passes' is 'foo' "foo" 'foo is foo' -ok "`true`" 'true is true' +ok "$(true)" 'true is true' -ok "`[ 123 -eq $((61+62)) ]`" 'Math works' +ok "$([ 123 -eq "$((61+62))" ])" 'Math works' -ok "`[[ ! team =~ I ]]`" "There's no I in team" +# shellcheck disable=2050 +ok "$([[ ! team =~ I ]])" "There's no I in team" # diag "A msg for stderr" note "A msg for stdout" + +expected=(line1 line2) + +command_output=(line1 line2 ) +cmp-array command_output expected "command output more" diff --git a/ext/test-more-bash/test/setup b/ext/test-more-bash/test/setup old mode 100644 new mode 100755 index 1c618959..99df35d6 --- a/ext/test-more-bash/test/setup +++ b/ext/test-more-bash/test/setup @@ -1,8 +1,8 @@ #!/usr/bin/env bash -set -e +set -e -u -o pipefail -BASHLIB="`find $PWD -type d | grep -E '/(bin|lib)$' | xargs -n1 printf "%s:"`" -PATH="$BASHLIB:$PATH" +BASHLIB=$(find "$PWD" -type d | grep -E '/(bin|lib)$' | xargs -n1 printf "%s:") +PATH=$BASHLIB:$PATH source bash+ :std diff --git a/ext/test-more-bash/test/shellcheck.t b/ext/test-more-bash/test/shellcheck.t new file mode 100644 index 00000000..a496d6fe --- /dev/null +++ b/ext/test-more-bash/test/shellcheck.t @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +source test/setup +use Test::More + +if ! command -v shellcheck >/dev/null; then + plan skip_all "The 'shellcheck' utility is not installed" +fi +if [[ ! $(shellcheck --version) =~ 0\.7\.1 ]]; then + plan skip_all "This test wants shellcheck version 0.7.1" +fi + +IFS=$'\n' read -d '' -r -a shell_files <<< "$( + find lib -type f + echo test/setup + find test -name '*.t' +)" || true + +skips=( + # We want to keep these 2 here always: + SC1090 # Can't follow non-constant source. Use a directive to specify location. + SC1091 # Not following: bash+ was not specified as input (see shellcheck -x). +) + +skip=$(IFS=,; echo "${skips[*]}") + +for file in "${shell_files[@]}"; do + [[ $file == *swp ]] && continue + is "$(shellcheck -e "$skip" "$file")" "" \ + "The shell file '$file' passes shellcheck" +done + +done_testing + +# vim: set ft=sh: diff --git a/ext/test-more-bash/test/test/fail1.t b/ext/test-more-bash/test/test/fail1.t index 4a4c25fe..4e702f04 100644 --- a/ext/test-more-bash/test/test/fail1.t +++ b/ext/test-more-bash/test/test/fail1.t @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# shellcheck disable=2034 + source test/setup use Test::More @@ -9,4 +11,15 @@ fail is foo bar 'is foo bar' -done_testing 3 +expected=(line1 line2) + +command_output=(line1 line2 line3) +cmp-array command_output expected "command output more" + +command_output=(line1) +cmp-array command_output expected "command output less" + +command_output=(line1 foo) +cmp-array command_output expected "command output diff" + +done_testing 6 diff --git a/lib/git-subrepo b/lib/git-subrepo index 68918cbe..dfd12017 100755 --- a/lib/git-subrepo +++ b/lib/git-subrepo @@ -1,34 +1,39 @@ #!/usr/bin/env bash # # -# Copyright 2013-2017 - Ingy döt Net +# Copyright 2013-2020 - Ingy döt Net # +# shellcheck disable=1090,1091,2034 # Exit on any errors: set -e +export FILTER_BRANCH_SQUELCH_WARNING=1 + # Import Bash+ helper functions: -SOURCE="$BASH_SOURCE" +SOURCE=${BASH_SOURCE[0]} while [[ -h $SOURCE ]]; do - DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" - SOURCE="$(readlink "$SOURCE")" - [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" + DIR=$( cd -P "$( dirname "$SOURCE" )" && pwd ) + SOURCE=$(readlink "$SOURCE") + [[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE done -SOURCE_DIR="$(dirname "$SOURCE")" +SOURCE_DIR=$(dirname "$SOURCE") -if [[ -z "$GIT_SUBREPO_ROOT" ]]; then +if [[ -z $GIT_SUBREPO_ROOT ]]; then # If `make install` installation used: source "${SOURCE_DIR}/git-subrepo.d/bash+.bash" else # If `source .rc` method used: source "${SOURCE_DIR}/../ext/bashplus/lib/bash+.bash" fi -bash+:import :std can +bash+:import :std can version-check + -VERSION=0.4.0 -REQUIRED_GIT_VERSION=2.7.0 -GIT_TMP=.git/tmp +VERSION=0.4.9 +REQUIRED_BASH_VERSION=4.0 +REQUIRED_GIT_VERSION=2.23.0 +GIT_TMP=$(git rev-parse --git-common-dir 2> /dev/null || echo .git)/tmp # `git rev-parse` turns this into a getopt parser and a command usage message: GETOPT_SPEC="\ @@ -66,9 +71,9 @@ b,branch= Specify the upstream branch to push/pull/fetch e,edit Edit commit message f,force Force certain operations F,fetch Fetch the upstream content first -M,method= Method when you join, valid options are 'merge' or 'rebase' - Default is 'merge' +M,method= Join method: 'merge' (default) or 'rebase' m,message= Specify a commit message +file= Specify a commit message file r,remote= Specify the upstream remote to push/pull/fetch s,squash Squash commits on push u,update Add the --branch and/or --remote overrides to .gitrepo @@ -104,7 +109,8 @@ main() { local subref= # Valid git ref format of subdir local gitrepo= # Path to .gitrepo file local worktree= # Worktree created by 'git worktree' - local start_pwd=$(pwd) # Store the original directory + local start_pwd + start_pwd=$(pwd) # Store the original directory local original_head_commit= # HEAD commit id at start of command local original_head_branch= # HEAD ref at start of command @@ -126,6 +132,7 @@ main() { local edit_wanted=false # Edit commit message using -e local wanted_commit_message= # Custom commit message using -m + local commit_msg_file= # Custom commit message using --file local join_method= # Current join method (rebase/merge) @@ -152,10 +159,14 @@ main() { command-init if $all_wanted && [[ ! $command =~ ^(help|status)$ ]]; then + if [[ -n $subrepo_branch ]]; then + error "options --branch and --all are not compatible" + fi + # Run the command on all subrepos local args=( "${command_arguments[@]}" ) get-all-subrepos - for subdir in ${subrepos[*]}; do + for subdir in "${subrepos[@]}"; do command-prepare subrepo_remote= subrepo_branch= @@ -193,15 +204,28 @@ command:clone() { # Successful command output: local re= $force_wanted && re=re - local remote="$subrepo_remote" + local remote=$subrepo_remote say "Subrepo '$remote' ($subrepo_branch) ${re}cloned into '$subdir'." } # `git subrepo init ` command: command:init() { command-setup +subdir - local remote="${subrepo_remote:=none}" - local branch="${subrepo_branch:=master}" + local default_branch= + # Check if Git supports init.defaultbranch (Git 2.28+) + local git_version + local git_major + local git_minor + git_version=$(git --version | sed 's/git version //' | sed 's/\..*//') + git_major=$(echo "$git_version" | cut -d. -f1) + git_minor=$(echo "$git_version" | cut -d. -f2) + if [[ $git_major -gt 2 ]] || [[ $git_major -eq 2 && $git_minor -ge 28 ]]; then + default_branch=$(git config --get init.defaultbranch 2>/dev/null || echo "master") + else + default_branch="master" + fi + local remote=${subrepo_remote:=none} + local branch=${subrepo_branch:=$default_branch} # Init new subrepo from the subdir: subrepo:init @@ -257,7 +281,7 @@ command:push() { # `git subrepo fetch ` command command:fetch() { command-setup +subdir - if [[ $subrepo_remote == "none" ]]; then + if [[ $subrepo_remote == none ]]; then say "Ignored '$subdir', no remote." else subrepo:fetch @@ -272,10 +296,10 @@ command:branch() { CALL subrepo:fetch fi - local branch="subrepo/$subref" + local branch=subrepo/$subref if $force_wanted; then # We must make sure that the worktree is removed as well - worktree="$GIT_TMP/$branch" + worktree=$GIT_TMP/$branch git:delete-branch "$branch" fi @@ -298,10 +322,10 @@ command:commit() { fi git:rev-exists "$refs_subrepo_fetch" || error "Can't find ref '$refs_subrepo_fetch'. Try using -F." - upstream_head_commit="$(git rev-parse "$refs_subrepo_fetch")" + upstream_head_commit=$(git rev-parse "$refs_subrepo_fetch") - [[ -n $subrepo_commit_ref ]] || - subrepo_commit_ref="subrepo/$subref" + [[ ${subrepo_commit_ref-} ]] || + subrepo_commit_ref=subrepo/$subref subrepo:commit say "Subrepo commit '$subrepo_commit_ref' committed as" @@ -315,11 +339,11 @@ command:status() { status-refs() { local output= - while read line; do + while read -r line; do [[ $line =~ ^([0-9a-f]+)\ refs/subrepo/$subref/([a-z]+) ]] || continue - local sha1=; sha1="$(git rev-parse --short "${BASH_REMATCH[1]}")" - local type="${BASH_REMATCH[2]}" - local ref="refs/subrepo/$subref/$type" + local sha1=; sha1=$(git rev-parse --short "${BASH_REMATCH[1]}") + local type=${BASH_REMATCH[2]} + local ref=refs/subrepo/$subref/$type if [[ $type == branch ]]; then output+=" Branch Ref: $sha1 ($ref)"$'\n' elif [[ $type == commit ]]; then @@ -332,8 +356,8 @@ status-refs() { output+=" Push Ref: $sha1 ($ref)"$'\n' fi done < <(git show-ref) - if [[ -n $output ]]; then - printf " Refs:\n$output" + if [[ $output ]]; then + printf " Refs:\n%s" "$output" fi } @@ -350,13 +374,14 @@ command:clean() { # Wrap git config $gitrepo command:config() { command-setup +subdir +config_option config_value - o "Update '$subdir' configuration with $config_option=$config_value" + # shellcheck disable=2154 + o "Update '$subdir' configuration with $config_option=${config_value-}" if [[ ! $config_option =~ ^(branch|cmdver|commit|method|remote|version)$ ]]; then error "Option $config_option not recognized" fi - if [[ -z $config_value ]]; then + if [[ -z ${config_value-} ]]; then OUT=true RUN git config --file="$gitrepo" "subrepo.$config_option" say "Subrepo '$subdir' option '$config_option' has value '$output'." return @@ -364,12 +389,12 @@ command:config() { if ! $force_wanted; then # Only allow changing method without force - if [[ ! $config_option == "method" ]]; then + if [[ $config_option != method ]]; then error "This option is autogenerated, use '--force' to override." fi fi - if [[ $config_option == "method" ]]; then + if [[ $config_option == method ]]; then if [[ ! $config_value =~ ^(merge|rebase)$ ]]; then error "Not a valid method. Valid options are 'merge' or 'rebase'." fi @@ -383,8 +408,8 @@ command:config() { # Launch the manpage viewer: command:help() { source "${SOURCE_DIR}/git-subrepo.d/help-functions.bash" - local cmd="${command_arguments[0]}" - if [[ -n $cmd ]]; then + local cmd=${command_arguments[0]} + if [[ $cmd ]]; then if can "help:$cmd"; then "help:$cmd" echo @@ -405,9 +430,9 @@ command:help() { command:version() { cat <<... git-subrepo Version: $VERSION -Copyright 2013-2017 Ingy döt Net -https://github.com/git-commands/git-subrepo -$BASH_SOURCE +Copyright 2013-2020 Ingy döt Net +https://github.com/ingydotnet/git-subrepo +${BASH_SOURCE[0]} Git Version: $git_version ... @@ -415,15 +440,15 @@ Git Version: $git_version } command:upgrade() { - local path="$0" + local path=$0 if [[ $path =~ ^/ && $path =~ ^(.*/git-subrepo)/lib/git-subrepo$ ]]; then - local subrepo_root="${BASH_REMATCH[1]}" + local subrepo_root=${BASH_REMATCH[1]} ( o "Change directory to '$subrepo_root'." cd "${BASH_REMATCH[1]}" - local branch="$(git rev-parse --abbrev-ref HEAD)" - if [[ $branch != master ]]; then + branch_name=$(git rev-parse --abbrev-ref HEAD) + if [[ $branch_name != master ]]; then error "git-subrepo repo is not on the 'master' branch" fi @@ -455,8 +480,6 @@ If you used 'make install' to install git-subrepo, then just do this: # Clone by fetching remote content into our subdir: subrepo:clone() { - re="$1" - FAIL=false RUN git rev-parse HEAD if ! OK; then error "You can't clone into an empty repository" @@ -472,18 +495,25 @@ subrepo:clone() { CALL subrepo:fetch read-gitrepo-file o "Check if we already are up to date." - if [[ $upstream_head_commit == $subrepo_commit ]]; then + if [[ $upstream_head_commit == "$subrepo_commit" ]]; then reclone_up_to_date=true return fi o "Remove the old subdir." RUN git rm -r -- "$subdir" + + if [[ -z $override_branch ]]; then + o "Determine the upstream head branch." + get-upstream-head-branch + subrepo_branch="$output" + override_branch="$output" # Force update of the branch in .gitrepo + fi else assert-subdir-empty if [[ -z $subrepo_branch ]]; then o "Determine the upstream head branch." get-upstream-head-branch - subrepo_branch="$output" + subrepo_branch=$output fi CALL subrepo:fetch @@ -493,13 +523,13 @@ subrepo:clone() { RUN mkdir -p -- "$subdir" o "Commit the new '$subdir/' content." - subrepo_commit_ref="$upstream_head_commit" + subrepo_commit_ref=$upstream_head_commit CALL subrepo:commit } # Init a new subrepo from current repo: subrepo:init() { - local branch_name="subrepo/${subref:??}" + local branch_name=subrepo/${subref:??} # Check if subdir is proper candidate for this init: assert-subdir-ready-for-init @@ -511,7 +541,7 @@ subrepo:init() { RUN git add -f -- "$gitrepo" o "Commit new subrepo to the '$original_head_branch' branch." - subrepo_commit_ref="$original_head_commit" + subrepo_commit_ref=$original_head_commit RUN git commit -m "$(get-commit-message)" o "Create ref '$refs_subrepo_commit'." @@ -522,22 +552,28 @@ subrepo:init() { subrepo:pull() { CALL subrepo:fetch + # If forced pull, then clone instead + if $force_wanted; then + CALL subrepo:clone + return + fi + # Check if we already are up to date # If the -u flag is present, always perform the operation - if [[ $upstream_head_commit == $subrepo_commit ]] && ! $update_wanted; then + if [[ $upstream_head_commit == "$subrepo_commit" ]] && ! $update_wanted; then OK=false; CODE=-1; return fi - local branch_name="subrepo/$subref" + local branch_name=subrepo/$subref git:delete-branch "$branch_name" - subrepo_commit_ref="$branch_name" + subrepo_commit_ref=$branch_name o "Create subrepo branch '$branch_name'." CALL subrepo:branch cd "$worktree"; - if [[ "$join_method" == "rebase" ]]; then + if [[ $join_method == rebase ]]; then o "Rebase changes to $refs_subrepo_fetch" FAIL=false OUT=true RUN git rebase "$refs_subrepo_fetch" "$branch_name" if ! OK; then @@ -549,7 +585,7 @@ subrepo:pull() { fi else o "Merge in changes from $refs_subrepo_fetch" - FAIL=false OUT=true RUN git merge "$refs_subrepo_fetch" + FAIL=false RUN git merge "$refs_subrepo_fetch" if ! OK; then say "The \"git merge\" command failed:" say @@ -571,7 +607,7 @@ subrepo:pull() { # Push a properly merged subrepo branch upstream: subrepo:push() { - local branch_name="$branch" + local branch_name=$branch local new_upstream=false local branch_created=false @@ -581,8 +617,9 @@ subrepo:push() { if ! OK; then # Check if we are pushing to a new upstream repo (or branch) and just # push the commit directly. This is common after a `git subrepo init`: - local re="(^|"$'\n'")fatal: Couldn't find remote ref " - if [[ $output =~ $re ]]; then + # Force to case in + local re="(^|"$'\n'")fatal: couldn't find remote ref " + if [[ ${output,,} =~ $re ]]; then o "Pushing to new upstream: $subrepo_remote ($subrepo_branch)." new_upstream=true else @@ -592,25 +629,27 @@ subrepo:push() { # Check that we are up to date: o "Check upstream head against .gitrepo commit." if ! $force_wanted; then - if [[ $upstream_head_commit != $subrepo_commit ]]; then + if [[ $upstream_head_commit != "$subrepo_commit" ]]; then error "There are new changes upstream, you need to pull first." fi fi fi - branch_name="subrepo/$subref" + branch_name=subrepo/$subref + # We must make sure that a stale worktree is removed as well + worktree=$GIT_TMP/$branch_name git:delete-branch "$branch_name" if $squash_wanted; then o "Squash commits" - subrepo_parent="HEAD^" + subrepo_parent=HEAD^ fi o "Create subrepo branch '$branch_name'." CALL subrepo:branch "$branch_name" cd "$worktree"; - if [[ "$join_method" == "rebase" ]]; then + if [[ $join_method == rebase ]]; then o "Rebase changes to $refs_subrepo_fetch" FAIL=false OUT=true RUN git rebase "$refs_subrepo_fetch" "$branch_name" if ! OK; then @@ -634,9 +673,13 @@ subrepo:push() { error "No subrepo branch '$branch_name' to push." o "Check if we have something to push" - new_upstream_head_commit="$(git rev-parse "$branch_name")" + new_upstream_head_commit=$(git rev-parse "$branch_name") if ! $new_upstream; then - if [[ $upstream_head_commit == $new_upstream_head_commit ]]; then + if [[ $upstream_head_commit == "$new_upstream_head_commit" ]]; then + if $branch_created; then + o "Remove branch '$branch_name'." + git:delete-branch "$branch_name" + fi OK=false CODE=-2 return @@ -655,6 +698,7 @@ subrepo:push() { "$force_wanted" && force=' --force' o "Push$force branch '$branch_name' to '$subrepo_remote' ($subrepo_branch)." + # shellcheck disable=2086 RUN git push$force "$subrepo_remote" "$branch_name":"$subrepo_branch" o "Create ref '$refs_subrepo_push' for branch '$branch_name'." @@ -666,10 +710,22 @@ subrepo:push() { fi o "Put updates into '$subdir/.gitrepo' file." - upstream_head_commit="$new_upstream_head_commit" - subrepo_commit_ref="$upstream_head_commit" + upstream_head_commit=$new_upstream_head_commit + subrepo_commit_ref=$upstream_head_commit update-gitrepo-file - RUN git commit -m "$(get-commit-message)" + + local commit_message + if [[ $wanted_commit_message ]]; then + commit_message=$wanted_commit_message + else + commit_message=$(get-commit-message) + fi + + if [[ $commit_msg_file ]]; then + RUN git command --file "$commit_msg_file" + else + RUN git commit -m "$commit_message" + fi } # Fetch the subrepo's remote branch content: @@ -684,15 +740,16 @@ subrepo:fetch() { o "Get the upstream subrepo HEAD commit." OUT=true RUN git rev-parse FETCH_HEAD^0 - upstream_head_commit="$output" + upstream_head_commit=$output o "Create ref '$refs_subrepo_fetch'." git:make-ref "$refs_subrepo_fetch" FETCH_HEAD^0 } # Create a subrepo branch containing all changes +# shellcheck disable=2120 subrepo:branch() { - local branch="${1:-"subrepo/$subref"}" + local branch=${1:-"subrepo/$subref"} o "Check if the '$branch' branch already exists." git:branch-exists "$branch" && return @@ -700,71 +757,97 @@ subrepo:branch() { local first_gitrepo_commit= o "Subrepo parent: $subrepo_parent" - if [[ -n "$subrepo_parent" ]]; then + + if [[ $subrepo_parent ]]; then + + # Check if the subrepo parent is an ancestor of HEAD. + # For example rebasing the commit that touched the subrepo + # could make the commit no longer match. + local parent_is_ancestor= + git merge-base --is-ancestor "$subrepo_parent" HEAD && parent_is_ancestor="true" + if [[ -z "$parent_is_ancestor" ]]; then + local prev_merge_point + prev_merge_point=$(git log -1 -G "commit =" --format="%H" "$gitrepo") + # We want the SHA of the patch before the sync point just in case someone made a modifcation in the + # actual sync commit + prev_merge_point=$(git log -1 --format="%H" "$prev_merge_point"^) + error "The last sync point (where upstream and the subrepo were equal) is not an ancestor. \n \ + This is usually caused by a rebase affecting that commit. \n \ + To recover set the subrepo parent in '$gitrepo'\n \ + to '$prev_merge_point' \n \ + and validate the subrepo by comparing with 'git subrepo branch $subdir'" + fi + local prev_commit= local ancestor= o "Create new commits with parents into the subrepo fetch" - OUT=true RUN git rev-list --reverse --ancestry-path "$subrepo_parent..HEAD" - local commit_list="$output" + OUT=true RUN git rev-list --reverse --ancestry-path --topo-order "$subrepo_parent..HEAD" + local commit_list=$output for commit in $commit_list; do o "Working on $commit" FAIL=false OUT=true RUN git config --blob \ - "$commit":"$subdir/.gitrepo" "subrepo.commit" - if [[ -z "$output" ]]; then + "$commit:$subdir/.gitrepo" "subrepo.commit" + if [[ -z $output ]]; then o "Ignore commit, no .gitrepo file" continue fi - local gitrepo_commit="$output" + local gitrepo_commit=$output o ".gitrepo reference commit: $gitrepo_commit" # Only include the commit if it's a child of the previous commit # This way we create a single path between $subrepo_parent..HEAD - if [[ -n "$ancestor" ]]; then - local is_direct_child=$(git show -s --pretty=format:"%P" $commit | grep "$ancestor") + if [[ $ancestor ]]; then + local is_direct_child + is_direct_child=$( + git show -s --pretty=format:"%P" "$commit" | + grep "$ancestor" + ) || true o "is child: $is_direct_child" - if [[ -z "$is_direct_child" ]]; then + if [[ -z $is_direct_child ]]; then o "Ignore $commit, it's not in the selected path" continue fi fi # Remember the previous commit from the parent repo path - ancestor="$commit" + ancestor=$commit o "Check for rebase" if git:rev-exists "$refs_subrepo_fetch"; then if ! git:commit-in-rev-list "$gitrepo_commit" "$refs_subrepo_fetch"; then - error "Local repository does not contain $gitrepo_commit. Try to 'git subrepo fetch $subref' of add the '-F' flag to always fetch the latest content." + error "Local repository does not contain $gitrepo_commit. Try to 'git subrepo fetch $subref' or add the '-F' flag to always fetch the latest content." fi fi o "Find parents" - local first_parent= - [[ -n $prev_commit ]] && first_parent="-p $prev_commit" - local second_parent= - if [[ -z "$first_gitrepo_commit" ]]; then - first_gitrepo_commit="$gitrepo_commit" - second_parent="-p $gitrepo_commit" + local first_parent second_parent + first_parent=() + [[ $prev_commit ]] && first_parent=(-p "$prev_commit") + second_parent=() + if [[ -z $first_gitrepo_commit ]]; then + first_gitrepo_commit=$gitrepo_commit + second_parent=(-p "$gitrepo_commit") fi - if [[ "$join_method" != "rebase" ]]; then + if [[ $join_method != rebase ]]; then # In the rebase case we don't create merge commits - if [[ "$gitrepo_commit" != "$last_gitrepo_commit" ]]; then - second_parent="-p $gitrepo_commit" - last_gitrepo_commit="$gitrepo_commit" + if [[ $gitrepo_commit != "$last_gitrepo_commit" ]]; then + second_parent=(-p "$gitrepo_commit") + last_gitrepo_commit=$gitrepo_commit fi fi - o "Create a new commit $first_parent $second_parent" + o "Create a new commit ${first_parent[*]} ${second_parent[*]}" FAIL=false RUN git cat-file -e "$commit":"$subdir" if OK; then o "Create with content" local PREVIOUS_IFS=$IFS IFS=$'\n' - local author_info=( $(git log -1 --format=%ad%n%ae%n%an "$commit") ) + local author_info + mapfile -t author_info < <(git log -1 --date=default --format=%ad%n%ae%n%an "$commit") IFS=$PREVIOUS_IFS # When we create new commits we leave the author information unchanged @@ -772,15 +855,15 @@ subrepo:branch() { # This should be analog how cherrypicking is handled allowing git # to store both the original author but also the responsible committer # that created the local version of the commit and pushed it. - prev_commit=$(git log -n 1 --format=%B "$commit" | - GIT_AUTHOR_DATE="${author_info[0]}" \ - GIT_AUTHOR_EMAIL="${author_info[1]}" \ - GIT_AUTHOR_NAME="${author_info[2]}" \ - git commit-tree -F - $first_parent $second_parent "$commit":"$subdir") + prev_commit=$(git log -n 1 --date=default --format=%B "$commit" | + GIT_AUTHOR_DATE=${author_info[0]} \ + GIT_AUTHOR_EMAIL=${author_info[1]} \ + GIT_AUTHOR_NAME=${author_info[2]} \ + git commit-tree -F - "${first_parent[@]}" "${second_parent[@]}" "$commit":"$subdir") else o "Create empty placeholder" prev_commit=$(git commit-tree -m "EMPTY" \ - $first_parent $second_parent "4b825dc642cb6eb9a060e54bf8d69288fbee4904") + "${first_parent[*]}" "${second_parent[*]}" "4b825dc642cb6eb9a060e54bf8d69288fbee4904") fi done @@ -794,10 +877,10 @@ subrepo:branch() { fi o "Remove the .gitrepo file from $first_gitrepo_commit..$branch" - local filter="$branch" - [[ -n "$first_gitrepo_commit" ]] && filter="$first_gitrepo_commit..$branch" + local filter=$branch + [[ $first_gitrepo_commit ]] && filter=$first_gitrepo_commit..$branch FAIL=false RUN git filter-branch -f --prune-empty --tree-filter \ - "rm -f .gitrepo" "$filter" + "rm -f .gitrepo" -- "$filter" --first-parent git:create-worktree "$branch" @@ -812,7 +895,7 @@ subrepo:commit() { error "Commit ref '$subrepo_commit_ref' does not exist." if ! "$force_wanted"; then - local upstream="$upstream_head_commit" + local upstream=$upstream_head_commit o "Make sure '$subrepo_commit_ref' contains the upstream HEAD." if ! git:commit-in-rev-list "$upstream" "$subrepo_commit_ref"; then error \ @@ -833,29 +916,37 @@ subrepo:commit() { RUN git add -f -- "$gitrepo" local commit_message - if [[ -n "$wanted_commit_message" ]]; then - commit_message="$wanted_commit_message" + if [[ $wanted_commit_message ]]; then + commit_message=$wanted_commit_message else - commit_message="$(get-commit-message)" + commit_message=$(get-commit-message) fi local edit_flag= $edit_wanted && edit_flag=--edit - [[ -n $commit_message ]] || commit_message="$(get-commit-message)" + [[ $commit_message ]] || commit_message=$(get-commit-message) local edit_flag= $edit_wanted && edit_flag=--edit o "Commit to the '$original_head_branch' branch." if [[ $original_head_commit != none ]]; then - RUN git commit $edit_flag -m "$commit_message" + if [[ $commit_msg_file ]]; then + RUN git commit $edit_flag --file "$commit_msg_file" + else + RUN git commit $edit_flag -m "$commit_message" + fi else # We had cloned into an empty repo, side effect of prior git reset --mixed # command is that subrepo's history is now part of the index. Commit # without that history. OUT=true RUN git write-tree - OUT=true RUN git commit-tree $edit_flag -m "$commit_message" "$output" + if [[ $commit_msg_file ]]; then + OUT=true RUN git commit-tree $edit_flag --file "$commit_msg_file" "$output" + else + OUT=true RUN git commit-tree $edit_flag -m "$commit_message" "$output" + fi RUN git reset --hard "$output" fi @@ -894,10 +985,10 @@ subrepo:status() { continue fi - refs_subrepo_fetch="refs/subrepo/$subref/fetch" - upstream_head_commit="$( + refs_subrepo_fetch=refs/subrepo/$subref/fetch + upstream_head_commit=$( git rev-parse --short "$refs_subrepo_fetch" 2> /dev/null || true - )" + ) subrepo_remote= subrepo_branch= @@ -914,27 +1005,31 @@ subrepo:status() { echo "Git subrepo '$subdir':" git:branch-exists "subrepo/$subref" && echo " Subrepo Branch: subrepo/$subref" - local remote="subrepo/$subref" + local remote=subrepo/$subref FAIL=false OUT=true RUN git config "remote.$remote.url" - [[ -n $output ]] && + [[ $output ]] && echo " Remote Name: subrepo/$subref" echo " Remote URL: $subrepo_remote" - [[ -n $upstream_head_commit ]] && + [[ $upstream_head_commit ]] && echo " Upstream Ref: $upstream_head_commit" echo " Tracking Branch: $subrepo_branch" [[ -z $subrepo_commit ]] || - echo " Pulled Commit: $(git rev-parse --short $subrepo_commit)" - if [[ -n $subrepo_parent ]]; then - echo " Pull Parent: $(git rev-parse --short $subrepo_parent)" + echo " Pulled Commit: $(git rev-parse --short "$subrepo_commit")" + if [[ $subrepo_parent ]]; then + echo " Pull Parent: $(git rev-parse --short "$subrepo_parent")" # TODO Remove this eventually: - elif [[ -n $subrepo_former ]]; then - printf " Former Commit: $(git rev-parse --short $subrepo_former)" + elif [[ $subrepo_former ]]; then + printf " Former Commit: %s" "$(git rev-parse --short "$subrepo_former")" echo " *** DEPRECATED ***" fi # Grep for directory, branch can be in detached state due to conflicts - local _worktree=$(git worktree list | grep "$GIT_TMP/subrepo/$subdir") - if [[ -n $_worktree ]]; then + local _worktree + _worktree=$( + git worktree list | + grep "$GIT_TMP/subrepo/$subdir " + ) || true + if [[ $_worktree ]]; then echo " Worktree: $_worktree" fi @@ -948,13 +1043,13 @@ subrepo:status() { subrepo:clean() { # Remove subrepo branches if exist: - local branch="subrepo/$subref" - local ref="refs/heads/$branch" - local worktree="$GIT_TMP/$branch" + local branch=subrepo/$subref + local ref=refs/heads/$branch + local worktree=$GIT_TMP/$branch o "Clean $subdir" git:remove-worktree - if [[ -e .git/$ref ]]; then + if git:branch-exists "$branch"; then o "Remove branch '$branch'." RUN git update-ref -d "$ref" clean_list+=("branch '$branch'") @@ -962,11 +1057,20 @@ subrepo:clean() { if "$force_wanted"; then o "Remove all subrepo refs." - if "$all_wanted"; then - RUN rm -fr .git/refs/subrepo/ - else - RUN rm -fr .git/refs/subrepo/$subref/ + local suffix='' + if ! $all_wanted; then + suffix=$subref/ fi + git show-ref | while read -r hash ref; do + if [[ $ref == "refs/subrepo/$suffix"* ]]; then + git update-ref -d "$ref" + fi + done + git show-ref | while read -r hash ref; do + if [[ $ref == "refs/original/refs/heads/subrepo/$suffix"* ]]; then + git update-ref -d "$ref" + fi + done fi } @@ -983,9 +1087,9 @@ subrepo:clean() { get-command-options() { [[ $# -eq 0 ]] && set -- --help - [[ -n $GIT_SUBREPO_QUIET ]] && quiet_wanted=true - [[ -n $GIT_SUBREPO_VERBOSE ]] && verbose_wanted=true - [[ -n $GIT_SUBREPO_DEBUG ]] && debug_wanted=true + [[ ${GIT_SUBREPO_QUIET-} ]] && quiet_wanted=true + [[ ${GIT_SUBREPO_VERBOSE-} ]] && verbose_wanted=true + [[ ${GIT_SUBREPO_DEBUG-} ]] && debug_wanted=true eval "$( echo "$GETOPT_SPEC" | @@ -994,28 +1098,30 @@ get-command-options() { )" while [[ $# -gt 0 ]]; do - local option="$1"; shift + local option=$1; shift case "$option" in --) break ;; -a) all_wanted=true ;; -A) ALL_wanted=true all_wanted=true ;; - -b) subrepo_branch="$1" - override_branch="$1" + -b) subrepo_branch=$1 + override_branch=$1 commit_msg_args+=("--branch=$1") shift ;; -e) edit_wanted=true ;; -f) force_wanted=true commit_msg_args+=("--force") ;; -F) fetch_wanted=true ;; - -m) wanted_commit_message="$1" - shift;; - -M) join_method="$1" + -m) + if [[ $commit_msg_file ]]; then + error "fatal: options '-m' and '--file' cannot be used together" + fi + wanted_commit_message=$1 shift;; - -M) join_method="$1" + -M) join_method=$1 shift;; - -r) subrepo_remote="$1" - override_remote="$1" + -r) subrepo_remote=$1 + override_remote=$1 commit_msg_args+=("--remote=$1") shift ;; -s) squash_wanted=true ;; @@ -1025,6 +1131,16 @@ get-command-options() { -v) verbose_wanted=true ;; -d) debug_wanted=true ;; -x) set -x ;; + --file) + if [[ $wanted_commit_message ]]; then + error "fatal: options '-m' and '--file' cannot be used together" + fi + if [ -f "$1" ]; then + commit_msg_file="$1" + else + error "Commit msg file at $1 not found" + fi + shift ;; --version) echo "$VERSION" exit ;; @@ -1033,34 +1149,34 @@ get-command-options() { done # Set subrepo command: - command="$1"; shift + command=$1; shift # Make sure command exists: can "command:$command" || usage-error "'$command' is not a command. See 'git subrepo help'." command_arguments=("$@") - if [[ ${#command_arguments} -gt 0 ]]; then - local first="${command_arguments[0]}" - first="${first%/}" - command_arguments[0]="$first" + if [[ ${command_arguments[*]-} && ${#command_arguments[@]} -gt 0 ]]; then + local first=${command_arguments[0]} + first=${first%/} + command_arguments[0]=$first fi commit_msg_args+=("${command_arguments[@]}") for option in all ALL edit fetch force squash; do - var="${option}_wanted" + var=${option}_wanted if ${!var}; then check_option $option fi done - if [[ -n $override_branch ]]; then + if [[ $override_branch ]]; then check_option branch fi - if [[ -n $override_remote ]]; then + if [[ $override_remote ]]; then check_option remote fi - if [[ -n $wanted_commit_message ]]; then + if [[ $wanted_commit_message || $commit_msg_file ]]; then check_option message fi if $update_wanted; then @@ -1077,13 +1193,13 @@ options_clean='ALL all force' options_clone='branch edit force message method' options_config='force' options_commit='edit fetch force message' -options_fetch='all branch remote' +options_fetch='all branch force remote' options_init='branch remote method' options_pull='all branch edit force message remote update' -options_push='all branch force remote squash update' +options_push='all branch force message remote squash update' options_status='ALL all fetch' check_option() { - local var="options_${command//-/_}" + local var=options_${command//-/_} [[ ${!var} =~ $1 ]] || usage-error "Invalid option '--$1' for '$command'." } @@ -1096,8 +1212,8 @@ command-init() { # Export variable to let other processes (possibly git hooks) know that they # are running under git-subrepo. Set to current process pid, so it can be # further verified if need be: - export GIT_SUBREPO_RUNNING="$$" - export GIT_SUBREPO_COMMAND="$command" + export GIT_SUBREPO_RUNNING=$$ + export GIT_SUBREPO_COMMAND=$command : "${GIT_SUBREPO_PAGER:=${PAGER:-less}}" if [[ $GIT_SUBREPO_PAGER == less ]]; then @@ -1110,7 +1226,7 @@ command-prepare() { if git:rev-exists HEAD; then git:get-head-branch-commit fi - original_head_commit="${output:-none}" + original_head_commit=${output:-none} } # Do the setup steps needed by most of the subrepo subcommands: @@ -1119,14 +1235,19 @@ command-setup() { check-and-normalize-subdir encode-subdir - gitrepo="$subdir/.gitrepo" + gitrepo=$subdir/.gitrepo if ! $force_wanted; then o "Check for worktree with branch subrepo/$subdir" - local _worktree=$(git worktree list | grep "\[subrepo/$subdir\]" | cut -d ' ' -f1) + local _worktree + _worktree=$( + git worktree list | + grep "\[subrepo/$subdir\]" | + cut -d ' ' -f1 + ) || true if [[ $command =~ ^(commit)$ && -z $_worktree ]]; then error "There is no worktree available, use the branch command first" - elif [[ ! $command =~ ^(branch|clean|commit|push)$ && -n $_worktree ]]; then + elif [[ ! $command =~ ^(branch|clean|commit|push)$ && $_worktree ]]; then if [[ -e $gitrepo ]]; then error "There is already a worktree with branch subrepo/$subdir. Use the --force flag to override this check or perform a subrepo clean @@ -1142,10 +1263,10 @@ Use the --force flag to override this check or remove the worktree with fi # Set refs_ variables: - refs_subrepo_branch="refs/subrepo/$subref/branch" - refs_subrepo_commit="refs/subrepo/$subref/commit" - refs_subrepo_fetch="refs/subrepo/$subref/fetch" - refs_subrepo_push="refs/subrepo/$subref/push" + refs_subrepo_branch=refs/subrepo/$subref/branch + refs_subrepo_commit=refs/subrepo/$subref/commit + refs_subrepo_fetch=refs/subrepo/$subref/fetch + refs_subrepo_push=refs/subrepo/$subref/push # Read/parse the .gitrepo file (unless clone/init; doesn't exist yet) if [[ ! $command =~ ^(clone|init)$ ]]; then @@ -1156,33 +1277,34 @@ Use the --force flag to override this check or remove the worktree with } # Parse command line args according to a simple dsl spec: +# shellcheck disable=2059 get-params() { local i=0 local num=${#command_arguments[@]} - for arg in $@; do - local value="${command_arguments[i]}" - value="${value//%/%%}" - value="${value//\\/\\\\}" + for arg in "$@"; do + local value=${command_arguments[i]-} + value=${value//%/%%} + value=${value//\\/\\\\} # If arg starts with '+' then it is required if [[ $arg == +* ]]; then if [[ $i -ge $num ]]; then usage-error "Command '$command' requires arg '${arg#+}'." fi - printf -v ${arg#+} -- "$value" + printf -v "${arg#+}" -- "$value" # Look for function name after ':' to provide a default value else if [[ $i -lt $num ]]; then - printf -v ${arg%:*} -- "$value" + printf -v "${arg%:*}" -- "$value" elif [[ $arg =~ : ]]; then "${arg#*:}" fi fi - let i=$((i+1)) + : $((i++)) done # Check for extra arguments: if [[ $num -gt $i ]]; then - set -- ${command_arguments[@]} + set -- "${command_arguments[@]}" for ((j = 1; j <= i; j++)); do shift; done error "Unknown argument(s) '$*' for '$command' command." fi @@ -1190,24 +1312,24 @@ get-params() { check-and-normalize-subdir() { # Sanity check subdir: - [[ -n $subdir ]] || + [[ $subdir ]] || die "subdir not set" [[ $subdir =~ ^/ || $subdir =~ ^[A-Z]: ]] && usage-error "The subdir '$subdir' should not be absolute path." - subdir="${subdir#./}" - subdir="${subdir%/}" + subdir=${subdir#./} + subdir=${subdir%/} [[ $subdir != *//* ]] || subdir=$(tr -s / <<< "$subdir") } # Determine the correct subdir path to use: guess-subdir() { - local dir="$subrepo_remote" - dir="${dir%.git}" - dir="${dir%/}" - dir="${dir##*/}" + local dir=$subrepo_remote + dir=${dir%.git} + dir=${dir%/} + dir=${dir##*/} [[ $dir =~ ^[-_a-zA-Z0-9]+$ ]] || error "Can't determine subdir from '$subrepo_remote'." - subdir="$dir" + subdir=$dir check-and-normalize-subdir encode-subdir } @@ -1254,7 +1376,8 @@ encode-subdir() { local i for (( i = 1; i < 32; ++i )); do # skip substitute NUL char (i=0), as bash will skip NUL in env - local x=$(printf "%02x" $i) + local x + x=$(printf "%02x" "$i") subref=${subref//$(printf "%b" "\x$x")/%$x} done subref=${subref//$'\177'/%7f} @@ -1298,7 +1421,7 @@ encode-subdir() { # Set subdir and gitrepo vars: read-gitrepo-file() { - gitrepo="$subdir/.gitrepo" + gitrepo=$subdir/.gitrepo if [[ ! -f $gitrepo ]]; then error "No '$gitrepo' file." @@ -1307,34 +1430,34 @@ read-gitrepo-file() { # Read .gitrepo values: if [[ -z $subrepo_remote ]]; then SAY=false OUT=true RUN git config --file="$gitrepo" subrepo.remote - subrepo_remote="$output" + subrepo_remote=$output fi if [[ -z $subrepo_branch ]]; then SAY=false OUT=true RUN git config --file="$gitrepo" subrepo.branch - subrepo_branch="$output" + subrepo_branch=$output fi SAY=false OUT=true RUN git config --file="$gitrepo" subrepo.commit - subrepo_commit="$output" + subrepo_commit=$output FAIL=false \ SAY=false OUT=true RUN git config --file="$gitrepo" subrepo.parent - subrepo_parent="$output" + subrepo_parent=$output FAIL=false \ SAY=false OUT=true RUN git config --file="$gitrepo" subrepo.method - if [[ $output == "rebase" ]]; then - join_method="rebase" + if [[ $output == rebase ]]; then + join_method=rebase else # This is the default method - join_method="merge" + join_method=merge fi if [[ -z $subrepo_parent ]]; then FAIL=false \ SAY=false OUT=true RUN git config --file="$gitrepo" subrepo.former - subrepo_former="$output" + subrepo_former=$output fi } @@ -1357,7 +1480,7 @@ update-gitrepo-file() { ; DO NOT EDIT (unless you know what you are doing) ; ; This subdirectory is a git "subrepo", and this file is maintained by the -; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme ; ... fi @@ -1365,25 +1488,25 @@ update-gitrepo-file() { # TODO: only update remote and branch if supplied and $update_wanted - if $newfile || [[ $update_wanted && -n $override_remote ]]; then + if $newfile || [[ $update_wanted && $override_remote ]]; then RUN git config --file="$gitrepo" subrepo.remote "$subrepo_remote" fi - if $newfile || [[ $update_wanted && -n $override_branch ]]; then + if $newfile || [[ $update_wanted && $override_branch ]]; then RUN git config --file="$gitrepo" subrepo.branch "$subrepo_branch" fi RUN git config --file="$gitrepo" subrepo.commit "$upstream_head_commit" # Only write new parent when we are at the head of upstream - if [[ -n $upstream_head_commit && -n $subrepo_commit_ref ]]; then + if [[ $upstream_head_commit && $subrepo_commit_ref ]]; then OUT=true RUN git rev-parse "$subrepo_commit_ref" o "$upstream_head_commit == $output" - if [[ $upstream_head_commit == $output ]]; then + if [[ $upstream_head_commit == "$output" ]]; then RUN git config --file="$gitrepo" subrepo.parent "$original_head_commit" fi fi - [[ -z $join_method ]] && join_method="merge" + [[ -z $join_method ]] && join_method=merge RUN git config --file="$gitrepo" subrepo.method "$join_method" RUN git config --file="$gitrepo" subrepo.cmdver "$VERSION" @@ -1402,15 +1525,30 @@ assert-environment-ok() { git_version=$(git --version | cut -d ' ' -f3) - if [[ $( - printf "$REQUIRED_GIT_VERSION\n$git_version" | - sort -t. -k 1,1n -k 2,2n -k 3,3n | - head -n1 - ) == "$git_version" && - $git_version != "$REQUIRED_GIT_VERSION" - ]]; then + version-check bash "$REQUIRED_BASH_VERSION" || { + echo "The 'bashplus' library requires that 'Bash ${REQUIRED_BASH_VERSION}+' is installed." >&2 + echo "It doesn't need to be your shell, but it must be in your PATH." >&2 + if [[ ${OSTYPE-} == darwin* ]]; then + echo "You appear to be on macOS." >&2 + echo "Try: 'brew install bash'." >&2 + echo "This will not change your user shell, it just installs 'Bash 5.x'." >&2 + fi + exit 1 + } + + version-check git "$REQUIRED_GIT_VERSION" || error "Requires git version $REQUIRED_GIT_VERSION or higher; "` `"you have '$git_version'." + + if [[ ${BASH_VERSINFO[0]} -lt 4 ]] ; then + echo "The git-subrepo command requires that 'Bash 4+' is installed." + echo "It doesn't need to be your shell, but it must be in your PATH." + if [[ $OSTYPE == darwin* ]]; then + echo "You appear to be on macOS." + echo "Try: 'brew install bash'." + echo "This will not change your user shell, it just installs 'Bash 5.x'." + fi + exit 1 fi } @@ -1425,7 +1563,7 @@ assert-repo-is-ready() { # Get the original branch and commit: git:get-head-branch-name - original_head_branch="$output" + original_head_branch=$output # If a subrepo branch is currently checked out, then note it: if [[ $original_head_branch =~ ^subrepo/(.*) ]]; then @@ -1448,7 +1586,7 @@ assert-repo-is-ready() { assert-working-copy-is-clean # For now, only support actions from top of repo: - if [[ -n "$(git rev-parse --show-prefix)" ]]; then + if [[ $(git rev-parse --show-prefix) ]]; then error "Need to run subrepo command from top level directory of the repo." fi } @@ -1457,7 +1595,8 @@ assert-working-copy-is-clean() { # Repo is in a clean state: if [[ $command =~ ^(clone|init|pull|push|branch|commit)$ ]]; then # TODO: Should we check for untracked files? - local pwd=$(pwd) + local pwd + pwd=$(pwd) o "Assert that working copy is clean: $pwd" git update-index -q --ignore-submodules --refresh git diff-files --quiet --ignore-submodules || @@ -1485,14 +1624,14 @@ assert-subdir-ready-for-init() { error "The subdir '$subdir' is already a subrepo." fi # Check that subdir is part of the repo - if [[ -z $(git log -1 -- $subdir) ]]; then + if [[ -z $(git log -1 --date=default -- "$subdir") ]]; then error "The subdir '$subdir' is not part of this repo." fi } # If subdir exists, make sure it is empty: assert-subdir-empty() { - if [[ -e $subdir ]] && [[ -n $(ls -A $subdir) ]]; then + if [[ -e $subdir ]] && [[ $(ls -A "$subdir") ]]; then error "The subdir '$subdir' exists and is not empty." fi } @@ -1504,13 +1643,8 @@ assert-subdir-empty() { # Find all the current subrepos by looking for all the subdirectories that # contain a `.gitrepo` file. get-all-subrepos() { - local paths=($( - find . -name '.gitrepo' | - grep -v '/.git/' | - grep '/.gitrepo$' | - sed 's/.gitrepo$//' | - sort - )) + local paths + mapfile -t paths < <(git ls-files | sed -n 's!/\.gitrepo$!!p' | sort) subrepos=() local path for path in "${paths[@]}"; do @@ -1521,7 +1655,7 @@ get-all-subrepos() { add-subrepo() { if ! $ALL_wanted; then for path in "${subrepos[@]}"; do - [[ $1 =~ ^$path ]] && return + [[ $1 =~ ^$path/ ]] && return done fi subrepos+=("$1") @@ -1529,31 +1663,28 @@ add-subrepo() { # Determine the upstream's default head branch: get-upstream-head-branch() { - OUT=true RUN git ls-remote $subrepo_remote - local remotes="$output" - [[ -n $remotes ]] || - error "Failed to 'git ls-remote $subrepo_remote'." - local commit="$( - echo "$remotes" | - grep HEAD | - cut -f1 - )" - local branch="$( + local remotes branch + OUT=true RUN git ls-remote --symref "$subrepo_remote" + remotes=$output + [[ $remotes ]] || + error "Failed to 'git ls-remote --symref $subrepo_remote'." + + # 'ref: refs/heads/master HEAD' + branch=$( echo "$remotes" | - grep -E "$commit[[:space:]]+refs/heads/" | - grep -v HEAD | - head -n1 | - cut -f2 - )" + grep "^ref:" | grep 'HEAD$' | cut -f2 -d':' | cut -f1 | + head -n1 + ) + branch=${branch/ } [[ $branch =~ refs/heads/ ]] || error "Problem finding remote default head branch." - output="${branch#refs/heads/}" + output=${branch#refs/heads/} } # Commit msg for an action commit: # Don't use RUN here as it will pollute commit message get-commit-message() { - local commit="none" + local commit=none if git:rev-exists "$upstream_head_commit"; then commit=$(git rev-parse --short "$upstream_head_commit") fi @@ -1562,19 +1693,19 @@ get-commit-message() { if $all_wanted; then args+=("$subdir") fi - args+=(${commit_msg_args[@]}) + args+=("${commit_msg_args[@]}") # Find the specific git-subrepo code used: local command_remote='???' local command_commit='???' get-command-info - local merged="none" + local merged=none if git:rev-exists "$subrepo_commit_ref"; then merged=$(git rev-parse --short "$subrepo_commit_ref") fi - local is_merge="" + local is_merge='' if [[ $command != push ]]; then if git:is_merge_commit "$subrepo_commit_ref"; then is_merge=" (merge)" @@ -1585,7 +1716,7 @@ get-commit-message() { # Format subrepo commit message: cat <<... -git subrepo $command$is_merge ${args[@]} +git subrepo $command$is_merge ${args[*]} subrepo: subdir: "$subdir" @@ -1605,9 +1736,10 @@ git-subrepo: # info goes into commit messages, so we can find out exactly how the commits # were done. get-command-info() { - local bin="$0" + local bin=$0 if [[ $bin =~ / ]]; then - local lib="$(dirname "$bin")" + local lib + lib=$(dirname "$bin") # XXX Makefile needs to install these symlinks: # If `git-subrepo` was system-installed (`make install`): if [[ -e $lib/git-subrepo.d/upstream ]] && @@ -1615,31 +1747,34 @@ get-command-info() { command_remote=$(readlink "$lib/git-subrepo.d/upstream") command_commit=$(readlink "$lib/git-subrepo.d/commit") elif [[ $lib =~ / ]]; then - lib="$(dirname "$lib")" + lib=$(dirname "$lib") if [[ -d $lib/.git ]]; then - local remote="$( + local remote + remote=$( GIT_DIR=$lib/.git git remote -v | grep '^origin' | head -n1 | cut -f2 | cut -d ' ' -f1 - )" - if [[ -n $remote ]]; then - command_remote="$remote" + ) + if [[ $remote ]]; then + command_remote=$remote else - local remote="$( + local remote + remote=$( GIT_DIR=$lib/.git git remote -v | head -n1 | cut -f2 | cut -d ' ' -f1 - )" - if [[ -n $remote ]]; then - command_remote="$remote" + ) + if [[ $remote ]]; then + command_remote=$remote fi fi - local commit="$(GIT_DIR="$lib/.git" git rev-parse --short HEAD)" - if [[ -n $commit ]]; then - command_commit="$commit" + local commit + commit=$(GIT_DIR=$lib/.git git rev-parse --short HEAD) + if [[ $commit ]]; then + command_commit=$commit fi fi fi @@ -1664,7 +1799,7 @@ This is the common conflict resolution workflow: 3. "git add" the resolved files. ... - if [[ "$join_method" == "rebase" ]]; then + if [[ $join_method == rebase ]]; then cat <<... 4. git rebase --continue ... @@ -1678,18 +1813,24 @@ This is the common conflict resolution workflow: 5. If there are more conflicts, restart at step 2. 6. cd $start_pwd ... - local branch_name="${branch:=subrepo/$subdir}" - if [[ "$command" == "push" ]]; then + local branch_name=${branch:=subrepo/$subdir} + if [[ $command == push ]]; then cat <<... 7. git subrepo push $subdir $branch_name ... else + if [[ $commit_msg_file ]]; then + cat <<... + 7. git subrepo commit --file=$commit_msg_file $subdir +... + else cat <<... 7. git subrepo commit $subdir ... + fi fi - if [[ "$command" == "pull" && "$join_method" == "rebase" ]]; then + if [[ $command == pull && $join_method == rebase ]]; then cat <<... After you have performed the steps above you can push your local changes @@ -1724,46 +1865,48 @@ git:rev-exists() { } git:ref-exists() { - test -n "$(git for-each-ref "$1")" + [[ $(git for-each-ref "$1") ]] } git:get-head-branch-name() { output= - local name="$(git symbolic-ref --short --quiet HEAD)" + local name + name=$(git symbolic-ref --short --quiet HEAD) || true [[ $name == HEAD ]] && return - output="$name" + output=$name } git:get-head-branch-commit() { - output="$(git rev-parse HEAD)" + output=$(git rev-parse HEAD) } git:commit-in-rev-list() { - local commit="$1" - local list_head="$2" + local commit=$1 + local list_head=$2 git rev-list "$list_head" | grep -q "^$commit" } git:make-ref() { - local ref_name="$1" - local commit="$(git rev-parse "$2")" + local ref_name=$1 + local commit + commit=$(git rev-parse "$2") RUN git update-ref "$ref_name" "$commit" } git:is_merge_commit() { - local commit="$1" + local commit=$1 git show --summary "$commit" | grep -q ^Merge: } git:create-worktree() { - local branch="$1" - worktree="$GIT_TMP/$branch" + local branch=$1 + worktree=$GIT_TMP/$branch RUN git worktree add "$worktree" "$branch" } git:remove-worktree() { o "Remove worktree: $worktree" - if [[ -d "$worktree" ]]; then + if [[ -d $worktree ]]; then o "Check worktree for unsaved changes" cd "$worktree" assert-working-copy-is-clean @@ -1776,7 +1919,7 @@ git:remove-worktree() { } git:delete-branch() { - local branch="$1" + local branch=$1 o "Deleting old '$branch' branch." # Remove worktree first, otherwise you can't delete the branch git:remove-worktree @@ -1790,7 +1933,7 @@ git:delete-branch() { # Smart command runner: RUN() { - $debug_wanted && $SAY && say '>>>' $* + $debug_wanted && $SAY && say ">>> $*" if $EXEC; then "$@" return $? @@ -1804,9 +1947,9 @@ RUN() { "$@" else if $OUT; then - out="$("$@" 2>/dev/null)" + out=$("$@" 2>/dev/null) else - out="$("$@" 2>&1)" + out=$("$@" 2>&1) fi fi rc=$? @@ -1816,7 +1959,7 @@ RUN() { OK=false $FAIL && error "Command failed: '$*'.\n$out" fi - output="$out" + output=$out } @@ -1837,7 +1980,7 @@ CALL() { # Print verbose steps for commands with steps: o() { if $verbose_wanted; then - echo "$INDENT* $@" + echo "$INDENT* $*" fi } @@ -1871,13 +2014,12 @@ usage-error() { # Nicely report common error messages: error() { - local msg="git-subrepo: $1" usage= - echo -e "$msg" >&2 + echo -e "git-subrepo:" "$@" >&2 exit 1 } # Start at the end: -[[ $BASH_SOURCE != "$0" ]] || main "$@" +[[ ${BASH_SOURCE[0]} != "$0" ]] || main "$@" # Local Variables: # tab-width: 2 diff --git a/lib/git-subrepo.d/help-functions.bash b/lib/git-subrepo.d/help-functions.bash index 10cda704..c7fc64db 100644 --- a/lib/git-subrepo.d/help-functions.bash +++ b/lib/git-subrepo.d/help-functions.bash @@ -1,5 +1,3 @@ -#!/usr/bin/env bash - # DO NOT EDIT. This file generated by pkg/bin/generate-help-functions.pl. set -e @@ -8,14 +6,14 @@ help:all() { cat <<'...' branch branch |--all [-f] [-F] clean clean |--all|--ALL [-f] -clone clone [] [-b ] [-f] [-m ] [-e] [--method ] -commit commit [] [-m ] [-e] [-f] [-F] +clone clone [] [-b ] [-f] [-m ] [--file=] [-e] [--method ] +commit commit [] [-m ] [--file=] [-e] [-f] [-F] config config