├── doc ├── .gitignore └── git-reintegrate.txt ├── t ├── .gitignore ├── Makefile ├── test-lib.sh ├── submodule.t ├── apply.t ├── fixup.t ├── prefix.t ├── conflicts.t ├── reintegrate.t └── sharness.sh ├── .travis.yml ├── shared └── git-reintegrate.bash ├── Makefile ├── README.asciidoc └── git-reintegrate /doc/.gitignore: -------------------------------------------------------------------------------- 1 | git-reintegrate.1 2 | -------------------------------------------------------------------------------- /t/.gitignore: -------------------------------------------------------------------------------- 1 | test-results/ 2 | trash directory.*/ 3 | .prove 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | script: 4 | - make test 5 | 6 | rvm: 7 | - ruby-head 8 | - 3.0 9 | - 2.7 10 | - 2.6 11 | -------------------------------------------------------------------------------- /t/Makefile: -------------------------------------------------------------------------------- 1 | RM ?= rm -f 2 | 3 | T = $(wildcard *.t) 4 | 5 | all: test 6 | 7 | test: $(T) 8 | $(MAKE) clean 9 | 10 | $(T): 11 | $(SHELL) $@ $(TEST_OPTS) 12 | 13 | clean: 14 | $(RM) -r 'trash directory'.* test-results 15 | 16 | .PHONY: all test $(T) clean 17 | -------------------------------------------------------------------------------- /shared/git-reintegrate.bash: -------------------------------------------------------------------------------- 1 | #!bash 2 | 3 | _git_reintegrate () { 4 | case "$cur" in 5 | --add=*) 6 | __gitcomp_nl "$(__git_refs)" "" "${cur##--add=}" 7 | return 8 | ;; 9 | -*) 10 | __gitcomp " 11 | --create --edit --rebuild --continue --abort 12 | --generate --cat --status 13 | --add= --prefix= 14 | --autocontinue" 15 | return 16 | ;; 17 | esac 18 | 19 | __gitcomp_nl "$(git --git-dir="$(__gitdir)" \ 20 | for-each-ref --format='%(refname)' refs/int | sed -e 's#^refs/int/##')" 21 | } 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix := $(HOME) 2 | 3 | bindir := $(prefix)/bin 4 | mandir := $(prefix)/share/man/man1 5 | 6 | all: doc 7 | 8 | doc: doc/git-reintegrate.1 9 | 10 | test: 11 | $(MAKE) -C t 12 | 13 | doc/git-reintegrate.1: doc/git-reintegrate.txt 14 | asciidoctor -b manpage $< 15 | 16 | clean: 17 | $(RM) doc/git-reintegrate.1 18 | 19 | D = $(DESTDIR) 20 | 21 | install: 22 | install -d -m 755 $(D)$(bindir)/ 23 | install -m 755 git-reintegrate $(D)$(bindir)/git-reintegrate 24 | 25 | install-doc: doc 26 | install -d -m 755 $(D)$(mandir)/ 27 | install -m 644 doc/git-reintegrate.1 $(D)$(mandir)/git-reintegrate.1 28 | 29 | .PHONY: all doc test install install-doc clean 30 | -------------------------------------------------------------------------------- /t/test-lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . "$(dirname "$0")"/sharness.sh 4 | 5 | GIT_AUTHOR_EMAIL=author@example.com 6 | GIT_AUTHOR_NAME='A U Thor' 7 | GIT_COMMITTER_EMAIL=committer@example.com 8 | GIT_COMMITTER_NAME='C O Mitter' 9 | export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME 10 | export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME 11 | 12 | unset GIT_EDITOR 13 | 14 | LF=' 15 | ' 16 | 17 | commit_file() { 18 | local filename="$1" 19 | echo "$2" > $filename && 20 | git add -f $filename && 21 | git commit -q -m "commit $filename" 22 | } 23 | 24 | write_script() { 25 | cat > "$1" && 26 | chmod +x "$1" 27 | } 28 | 29 | test_cmp() { 30 | diff -u "$@" 31 | } 32 | 33 | test_must_fail() { 34 | "$@" 35 | exit_code=$? 36 | if test $exit_code = 0; then 37 | echo >&2 "test_must_fail: command succeeded: $*" 38 | return 1 39 | elif test $exit_code -gt 129 -a $exit_code -le 192; then 40 | echo >&2 "test_must_fail: died by signal: $*" 41 | return 1 42 | elif test $exit_code = 127; then 43 | echo >&2 "test_must_fail: command not found: $*" 44 | return 1 45 | fi 46 | return 0 47 | } 48 | 49 | test_when_finished() { 50 | test_cleanup="{ $* 51 | } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" 52 | } 53 | -------------------------------------------------------------------------------- /t/submodule.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright (C) 2021 Hubic SAS 5 | # 6 | # This file may be used under the terms of the GNU GPL version 2. 7 | # 8 | 9 | test_description="Test submodule reintegration" 10 | 11 | . "$(dirname "$0")"/test-lib.sh 12 | 13 | test_expect_success 'setup branches' ' 14 | git init -q && 15 | git config rerere.enabled true && 16 | commit_file base base && 17 | git init -q sub && 18 | (cd sub && commit_file subbase subbase) && 19 | git submodule add -- ./ sub && 20 | git commit -q -m "add submodule" && 21 | git checkout -b branch1 master && 22 | commit_file branch branch1 && 23 | git checkout -b branch2 master && 24 | commit_file branch branch2 && 25 | git checkout -b branch3 master && 26 | (cd sub && commit_file subcontents subcontents) && 27 | git commit sub -m "submodule change" && 28 | git checkout master && 29 | git submodule update 30 | ' 31 | 32 | test_expect_success 'integrate a submodule change' ' 33 | git checkout master && 34 | git reintegrate --create pu && 35 | git reintegrate --add=branch1 && 36 | git reintegrate --apply && 37 | git reintegrate --add=branch2 && 38 | test_must_fail git reintegrate --apply && 39 | echo "branch12" > branch && 40 | git add branch && 41 | git reintegrate --continue && 42 | git reintegrate --add=branch3 && 43 | git reintegrate --apply && 44 | git submodule update 45 | ' 46 | 47 | test_expect_success 'reintegrate' ' 48 | git ls-tree HEAD^: > expected && 49 | git reintegrate --rebuild --autocontinue && 50 | git ls-tree HEAD^: > actual && 51 | test_cmp expected actual 52 | ' 53 | 54 | test_done 55 | -------------------------------------------------------------------------------- /t/apply.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright (C) 2014 Felipe Contreras 5 | # 6 | # This file may be used under the terms of the GNU GPL version 2. 7 | # 8 | 9 | test_description='Test git reintegrage apply option' 10 | 11 | . "$(dirname "$0")"/test-lib.sh 12 | 13 | test_expect_success 'setup branches' ' 14 | git init -q && 15 | commit_file base base && 16 | 17 | git checkout -b feature-1 master && 18 | commit_file feature-1 feature-1 && 19 | git checkout -b feature-2 master && 20 | commit_file feature-2 feature-2 && 21 | 22 | git checkout -b next master && 23 | git merge --no-ff feature-1 && 24 | git merge --no-ff feature-2 25 | ' 26 | 27 | test_expect_success 'generate integration' ' 28 | git reintegrate --create integration && 29 | git reintegrate --add=feature-1 --add=feature-2 && 30 | git reintegrate --rebuild && 31 | > expected && 32 | git diff next integration > actual && 33 | test_cmp expected actual 34 | ' 35 | 36 | test_expect_success 'update integration' ' 37 | git checkout feature-1 && 38 | commit_file feature-1-fix feature-1-fix && 39 | git reintegrate --rebuild integration && 40 | git diff feature-1^..feature-1 > expected && 41 | git diff next integration > actual && 42 | test_cmp expected actual 43 | ' 44 | 45 | cat > expected < actual && 55 | test_cmp expected actual && 56 | > expected && 57 | git diff next integration > actual && 58 | test_cmp expected actual 59 | ' 60 | 61 | test_done 62 | -------------------------------------------------------------------------------- /t/fixup.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright (C) 2013-2014 Felipe Contreras 5 | # Copyright (C) 2013 John Keeping 6 | # 7 | # This file may be used under the terms of the GNU GPL version 2. 8 | # 9 | 10 | test_description='Test the "fixup" instruction' 11 | 12 | . "$(dirname "$0")"/test-lib.sh 13 | 14 | test_expect_success 'setup branches' ' 15 | git init -q && 16 | commit_file base base && 17 | git checkout -b branch1 && 18 | commit_file branch1 branch1 && 19 | git checkout -b branch2 master && 20 | commit_file branch2 branch2 21 | ' 22 | 23 | write_script .git/EDITOR <<\EOF 24 | #!/bin/sh 25 | cat >> "$1" <> "$1" < actual && 53 | echo refs/heads/pu > expect && 54 | test_cmp expect actual && 55 | git merge-base --is-ancestor branch1 HEAD && 56 | git merge-base --is-ancestor branch2 HEAD 57 | ' 58 | 59 | test_expect_success 'check fixup applied' ' 60 | echo fixup > expect && 61 | test_cmp expect fixup 62 | ' 63 | 64 | test_expect_success 'check fixup squashed' ' 65 | git log --oneline -- fixup > actual && 66 | grep "branch .branch2. into pu" actual && 67 | test $(wc -l &2 "Expected only a single commit touching fixup" 69 | exit 1 70 | ) 71 | ' 72 | 73 | test_done 74 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | = git-reintegrate = 2 | 3 | This tool helps to manage integration branches. 4 | 5 | For example, say you have a repository with three branches: 6 | 7 | * feature-a 8 | * feature-b 9 | * maint 10 | 11 | And you have an integration branch named 'integration' where you merge all 12 | these branches on top of 'master'. 13 | 14 | You can generate the instructions needed by `git reintegrate` with this 15 | command: 16 | 17 | ------------ 18 | git reintegrate --generate integration master 19 | ------------ 20 | 21 | Which would generate instructions like: 22 | 23 | ------------ 24 | base master 25 | merge feature-a 26 | 27 | Merge work in progress feature-a 28 | 29 | merge feature-b 30 | 31 | Merge feature-b 32 | 33 | merge maint 34 | 35 | Merge good stuff 36 | ------------ 37 | 38 | You can edit the instructions with `git reintegrate --edit`. 39 | 40 | The simplest way to begin an integration branch is with: 41 | 42 | ------------ 43 | git reintegrate --create integration master 44 | git reintegrate --add=branch1 --add=branch2 --add=branch3 45 | ------------ 46 | 47 | To regenerate the integration branch run `git reintegrate --rebuild`, if there 48 | are merge conflicts, solve them and continue with `git reintegrate --continue`. 49 | 50 | You probably want to configure `git rerere` so that each time you resolve a 51 | conflict it gets automatically stored, so the next time Git sees the conflict, 52 | it's resolved automatically: 53 | 54 | ------------ 55 | git config --global rerere.enabled true 56 | ------------ 57 | 58 | == Installation == 59 | 60 | Simply copy the script anywhere in your '$PATH' and make it executable, or run 61 | `make install` which will install it by default to your '~/bin/' directory 62 | (make sure it's in your '$PATH'). 63 | 64 | == Acknowledgements == 65 | 66 | This is a rewrite of John Keeping's `git integration` tool 67 | (https://github.com/johnkeeping/git-integration[link]) , that provides a 68 | one-to-one mapping of functionality, plus some extras. Also, it borrows ideas 69 | from git.git's integration scripts. 70 | -------------------------------------------------------------------------------- /t/prefix.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright (C) 2013-2021 Felipe Contreras 5 | # Copyright (C) 2013 John Keeping 6 | # 7 | # This file may be used under the terms of the GNU GPL version 2. 8 | # 9 | 10 | test_description="Test git reintegrate prefix support" 11 | 12 | . "$(dirname "$0")"/test-lib.sh 13 | 14 | prefix="sub/" 15 | git config --global integration.prefix $prefix 16 | 17 | test_expect_success 'setup' ' 18 | git init -q && 19 | commit_file base base && 20 | git checkout -b sub/master master && 21 | git checkout -b sub/branch1 sub/master && 22 | commit_file branch1 branch1 && 23 | git checkout -b sub/branch2 sub/master && 24 | commit_file branch2 branch2 && 25 | git reintegrate --create pu 26 | ' 27 | 28 | write_script .git/EDITOR <<\EOF 29 | #!/bin/sh 30 | cat >> "$1" < expected 53 | ) && 54 | 55 | git log --merges --format="* %B" > actual && 56 | test_cmp expected actual 57 | } 58 | 59 | test_expect_success 'add branches to integration branch' ' 60 | GIT_EDITOR=".git/EDITOR" git reintegrate --edit pu && 61 | git reintegrate --rebuild && 62 | check_int pu <<-EOF 63 | branch2:This merges branch 2. 64 | branch1:This merges branch 1. 65 | EOF 66 | ' 67 | 68 | write_script .git/EDITOR <<\EOF 69 | #!/bin/sh 70 | cat >> "$1" < "$1" < actual && 38 | echo refs/heads/pu > expect && 39 | test_cmp expect actual 40 | ' 41 | 42 | test_expect_success 'conflict in last branch resolved' ' 43 | test_must_fail git reintegrate --rebuild && 44 | git merge-base --is-ancestor branch1 HEAD && 45 | test_must_fail git merge-base --is-ancestor branch2 HEAD && 46 | echo resolved > base && 47 | git add base && 48 | git reintegrate --continue > output && 49 | cat output && 50 | grep -q branch2 output && 51 | git merge-base --is-ancestor branch2 HEAD 52 | ' 53 | 54 | test_expect_success 'conflict in last branch try continue when unresolved' ' 55 | test_must_fail git reintegrate --rebuild && 56 | git merge-base --is-ancestor branch1 HEAD && 57 | test_must_fail git merge-base --is-ancestor branch2 HEAD && 58 | test_must_fail git reintegrate --continue && 59 | echo resolved > base && 60 | git add base && 61 | git reintegrate --continue > output && 62 | cat output && 63 | grep -q branch2 output && 64 | git merge-base --is-ancestor branch2 HEAD 65 | ' 66 | 67 | test_expect_success 'conflict in last branch and abort' ' 68 | git checkout pu && 69 | git reset --hard master && 70 | test_must_fail git reintegrate --rebuild && 71 | git merge-base --is-ancestor branch1 HEAD && 72 | test_must_fail git merge-base --is-ancestor branch2 HEAD && 73 | git reintegrate --abort && 74 | git rev-parse --verify master > expect && 75 | git rev-parse --verify pu > actual && 76 | test_cmp expect actual && 77 | echo refs/heads/pu > expect && 78 | git symbolic-ref HEAD > actual && 79 | test_cmp expect actual && 80 | test_must_fail git merge-base --is-ancestor branch1 HEAD && 81 | test_must_fail git merge-base --is-ancestor branch2 HEAD 82 | ' 83 | 84 | test_expect_success 'abort does not move other branches' ' 85 | git checkout pu && 86 | git reset --hard master && 87 | git rev-parse --verify branch1 > expect && 88 | test_must_fail git reintegrate --rebuild && 89 | git checkout --force branch1 && 90 | git reintegrate --abort && 91 | git rev-parse --verify branch1 > actual && 92 | test_cmp expect actual 93 | ' 94 | 95 | write_script .git/EDITOR <<\EOF 96 | #!/bin/sh 97 | cat >> "$1" < base && 112 | git add base && 113 | git reintegrate --continue > output && 114 | cat output && 115 | grep -q branch2 output && 116 | grep -q branch3 output && 117 | git merge-base --is-ancestor branch2 HEAD && 118 | git merge-base --is-ancestor branch3 HEAD 119 | ' 120 | 121 | test_done 122 | -------------------------------------------------------------------------------- /t/reintegrate.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright (C) 2013-2014 Felipe Contreras 5 | # Copyright (C) 2013 John Keeping 6 | # 7 | # This file may be used under the terms of the GNU GPL version 2. 8 | # 9 | 10 | test_description="Test git reintegrate" 11 | 12 | . "$(dirname "$0")"/test-lib.sh 13 | 14 | test_expect_success 'setup branches' ' 15 | git init -q && 16 | commit_file base base && 17 | git checkout -b branch1 master && 18 | commit_file branch1 branch1 && 19 | git checkout -b branch2 master && 20 | commit_file branch2 branch2 21 | ' 22 | 23 | test_expect_success 'create integration branch' ' 24 | git checkout master && 25 | git reintegrate --create pu && 26 | git reintegrate --cat > actual && 27 | echo "base master" > expect && 28 | test_cmp expect actual && 29 | git symbolic-ref HEAD > actual && 30 | echo refs/heads/pu > expect && 31 | test_cmp expect actual 32 | ' 33 | 34 | write_script .git/EDITOR <<\EOF 35 | #!/bin/sh 36 | cat >> "$1" < expected 59 | ) && 60 | 61 | git log --merges --format="* %B" > actual && 62 | test_cmp expected actual 63 | } 64 | 65 | test_expect_success 'add branches to integration branch' ' 66 | GIT_EDITOR=".git/EDITOR" git reintegrate --edit && 67 | git reintegrate --rebuild && 68 | git merge-base --is-ancestor branch1 HEAD && 69 | git merge-base --is-ancestor branch2 HEAD && 70 | test_must_fail git merge-base --is-ancestor branch3 HEAD && 71 | check_int pu <<-EOF 72 | branch2:This merges branch 2. 73 | branch1:This merges branch 1. 74 | EOF 75 | ' 76 | 77 | write_script .git/EDITOR <<\EOF 78 | #!/bin/sh 79 | cat >> "$1" < expect && 103 | GIT_EDITOR=true git reintegrate --edit && 104 | git rev-parse --verify refs/int/pu > actual && 105 | test_cmp expect actual 106 | ' 107 | 108 | test_expect_success 'generate instructions' ' 109 | git init -q tmp && 110 | test_when_finished "rm -rf tmp" && 111 | ( 112 | cd tmp && 113 | commit_file base base && 114 | git checkout -b branch1 master && 115 | commit_file branch1 branch1 && 116 | git checkout -b branch2 master && 117 | commit_file branch2 branch2 && 118 | git checkout -b branch3 master && 119 | commit_file branch3 branch3 && 120 | git checkout -b pu master && 121 | git merge --no-ff branch1 && 122 | git merge --no-ff branch2 && 123 | git merge --no-ff branch3 && 124 | git checkout branch1 && 125 | commit_file branch1 branch1-update && 126 | git reintegrate --generate pu master && 127 | git reintegrate --cat pu > ../actual 128 | ) && 129 | cat > expected <<-EOF && 130 | base master 131 | merge branch1~1 132 | merge branch2 133 | merge branch3 134 | EOF 135 | test_cmp expected actual 136 | ' 137 | 138 | write_script .git/EDITOR <<\EOF 139 | #!/bin/sh 140 | cat >> "$1" < actual && 151 | cat > expected <<-EOF && 152 | Empty commit. 153 | 154 | EOF 155 | test_cmp expected actual 156 | ' 157 | 158 | write_script .git/EDITOR <<\EOF 159 | #!/bin/sh 160 | cat > "$1" < [] 12 | 'git-reintegrate' --generate [] 13 | 'git-reintegrate' --add= 14 | 'git-reintegrate' (--edit | --rebuild | --apply | --cat | --status) [] 15 | 'git reintegrate' (--continue | --abort) 16 | 'git-reintegrate' --list 17 | 18 | DESCRIPTION 19 | ----------- 20 | This tool is a helper to be able to manage integration branches in Git easily. 21 | It does so by specifying a list of merges to be applied on top of a base 22 | branch. Each one of these merges can have a description that will be used as 23 | the merge commit message. 24 | 25 | This instruction sheet can be autogenerated and modified in various ways through 26 | the command line, or manually edited. 27 | 28 | Finally the integration branch can be rebuilt, and previous conflicts 29 | resolutions can be reused thanks to `git rerere`. 30 | 31 | OPTIONS 32 | ------- 33 | --create:: 34 | Create a new integration branch. 35 | + 36 | If no base is specified, 'master' is assumed. 37 | 38 | --generate:: 39 | Generates the instruction sheet based on an existing integration 40 | branch. The messages of each merge commit will be parsed to construct 41 | the instructions. The messages should be in the standard form 42 | `Merge branch 'name'` (or remote branch). 43 | + 44 | If no base is specified, 'master' is assumed. 45 | 46 | --[no-]rebuild:: 47 | Rebuild the integration branch. 48 | 49 | --edit:: 50 | Edit the instruction sheet. 51 | 52 | --cat:: 53 | Print the instruction sheet. 54 | 55 | --status:: 56 | Prints the status of the integration branch. 57 | 58 | --add=:: 59 | Appends a line `merge ` to the instruction list, causing that 60 | branch to be included in the integration branch when it is next 61 | rebuilt. This option may be specified multiple times to add multiple 62 | branches. 63 | 64 | --continue:: 65 | Restart the rebuild process after having resolved a merge conflict. 66 | 67 | --abort:: 68 | Abort the rebuild operation. 69 | 70 | --[no-]autocontinue:: 71 | Continues automatically if the rerere mechanism manages to resolve all 72 | conflicts during a merge. 73 | 74 | --list:: 75 | List all the current integration branches. 76 | 77 | --delete:: 78 | Delete an integration branch. It doesn't delete the branch itself, only 79 | the integration information. 80 | 81 | --apply:: 82 | Applies the instruction sheet of the integration branch on top of the 83 | current branch. If a branch to merge is already completely merged in 84 | the current branch, it's skipped. 85 | 86 | --prefix:: 87 | Add a prefix to each branch in the instruction sheet. So if the 88 | instruction says 'merge feature-a' it gets transpated to 'merge 89 | prefix/feature-a'. 90 | 91 | If `--continue` or `--abort` are specified then no other options may be given. 92 | 93 | CONFIGURATION 94 | ------------- 95 | 96 | integration.autocontinue:: 97 | Sets the default for the `--autocontinue` option. 98 | 99 | integration.autorebuild:: 100 | Automatically rebuild the integration branch after creating/editing it 101 | if `--no-rebuild` is not specified. 102 | 103 | integration.prefix:: 104 | Automatically adds a prefix to each branch as if `--prefix` was 105 | specified. `--prefix` overrides this value. 106 | 107 | FORMAT OF INSTRUCTIONS 108 | ---------------------- 109 | The instruction sheet consists of a series of instructions which begin in 110 | column zero, each of which may be followed by indented comments. The 111 | following instructions are supported: 112 | 113 | +base+ '':: 114 | Resets the state of the integration branch to the specified revision. 115 | This should always be the first instruction in the instruction sheet, 116 | and should appear only at the top of the instruction sheet. 117 | 118 | +merge+ '' '[]':: 119 | Merges the specified ref into the integration branch. Any comments 120 | following the instruction are appended to the merge commit's commit 121 | message. 122 | + 123 | If any options are given after the ref (and on the same line) then these are 124 | passed to 'git merge'. This may be useful for specifying an alternative merge 125 | strategy for a branch. 126 | 127 | +fixup+ '':: 128 | Picks up an existing commit to be applied on top of a merge as a fixup. 129 | 130 | +commit+:: 131 | Adds an empty commit with the specified message mostly to be used as a 132 | comment. 133 | 134 | +pause+:: 135 | Pauses the rebuild process, so the user can do certain actions 136 | manually, for example fixing a wrong conflict resolution. 137 | 138 | +.+ '':: 139 | Ignores this command and its description. This can be used to remove 140 | a branch from the integration branch without needing to delete it and 141 | its description from the instruction sheet. 142 | 143 | Example 144 | ~~~~~~~ 145 | ------ 146 | base master 147 | 148 | merge my-experimental-feature 149 | 150 | I think this is a good idea, but want to dogfood it before I 151 | decide whether to submit it upstream. 152 | 153 | merge my-site-specific-changes 154 | 155 | Some changes to suit my environment. DO NOT SUBMIT THESE. 156 | ------ 157 | -------------------------------------------------------------------------------- /git-reintegrate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # 4 | # Copyright (C) 2013-2014 Felipe Contreras 5 | # 6 | # This file may be used under the terms of the GNU GPL version 2. 7 | # 8 | 9 | require 'fileutils' 10 | 11 | $merged = [] 12 | $actions = [] 13 | 14 | $need_rebuild = false 15 | $branches_to_add = [] 16 | $autocontinue = false 17 | $prefix = nil 18 | 19 | NULL_SHA1 = '0' * 40 20 | 21 | def die(*args) 22 | fmt = args.shift 23 | $stderr.printf("fatal: %s\n" % fmt, *args) 24 | exit 128 25 | end 26 | 27 | def git_editor(*args) 28 | editor = %x[git var GIT_EDITOR].chomp.split(' ') 29 | system(*editor, *args) 30 | end 31 | 32 | class ParseOpt 33 | attr_writer :usage 34 | 35 | class Option 36 | attr_reader :short, :long, :help 37 | 38 | def initialize(short, long, help, &block) 39 | @block = block 40 | @short = short 41 | @long = long 42 | @help = help 43 | end 44 | 45 | def call(v) 46 | @block.call(v) 47 | end 48 | end 49 | 50 | def initialize 51 | @list = {} 52 | end 53 | 54 | def on(short = nil, long = nil, help = nil, &block) 55 | opt = Option.new(short, long, help, &block) 56 | @list[short] = opt if short 57 | @list[long] = opt if long 58 | end 59 | 60 | def parse 61 | if ARGV.member?('-h') or ARGV.member?('--help') 62 | usage 63 | exit 0 64 | end 65 | seen_dash = false 66 | ARGV.delete_if do |cur| 67 | opt = val = nil 68 | next false if cur[0,1] != '-' or seen_dash 69 | case cur 70 | when '--' 71 | seen_dash = true 72 | next true 73 | when /^--no-(.+)$/ 74 | opt = @list[$1] 75 | val = false 76 | when /^-([^-])(.+)?$/, /^--(.+?)(?:=(.+))?$/ 77 | opt = @list[$1] 78 | val = $2 || true 79 | end 80 | if opt 81 | opt.call(val) 82 | true 83 | else 84 | usage 85 | exit 1 86 | end 87 | end 88 | end 89 | 90 | def usage 91 | def fmt(prefix, str) 92 | return str ? prefix + str : nil 93 | end 94 | puts 'usage: %s' % @usage 95 | @list.values.uniq.each do |opt| 96 | s = ' ' 97 | s << '' 98 | s << [fmt('-', opt.short), fmt('--', opt.long)].compact.join(', ') 99 | s << '' 100 | s << '%*s%s' % [26 - s.size, '', opt.help] if opt.help 101 | puts s 102 | end 103 | end 104 | 105 | end 106 | 107 | def parse_merge(other, commit, msg, body) 108 | case msg 109 | when /^Merge branch '(.*)'/ 110 | ref = 'refs/heads/' + $1 111 | when /^Merge remote branch '(.*)'/ 112 | ref = 'refs/' + $1 113 | else 114 | $stderr.puts "Huh?: #{msg}" 115 | return 116 | end 117 | 118 | merged = %x[git name-rev --refs="#{ref}" "#{other}" 2> /dev/null].chomp 119 | if merged =~ /\h{40} (.*)/ 120 | merged = $1 121 | end 122 | merged = "merge #{merged}" 123 | merged += "\n\n" + body.gsub(/^/, ' ') unless body.empty? 124 | merged 125 | end 126 | 127 | class Branch 128 | 129 | attr_reader :name, :ref, :int 130 | attr_reader :into_name, :into_ref 131 | 132 | def initialize(name) 133 | @name = name 134 | end 135 | 136 | def into_name=(name) 137 | @into_name = name 138 | @into_ref = %x[git rev-parse --symbolic-full-name "refs/heads/#{name}"].chomp 139 | end 140 | 141 | def get 142 | if @name 143 | @ref = %x[git rev-parse --symbolic-full-name "refs/heads/#{@name}"].chomp 144 | die "no such branch: #{@name}" unless $?.success? 145 | else 146 | if test('f', $head_file) 147 | @name = File.read($head_file) 148 | else 149 | @ref = %x[git symbolic-ref HEAD].chomp 150 | die "HEAD is detached, could not figure out which integration branch to use" unless $?.success? 151 | @name = @ref.gsub(%r{^refs/heads/}, '') 152 | end 153 | end 154 | 155 | @int = 'refs/int/' + @name 156 | 157 | system(*%w[git rev-parse --quiet --verify], @int, :out => File::NULL) 158 | die "Not an integration branch: #{@name}" unless $?.success? 159 | end 160 | 161 | def create(base = nil) 162 | @ref = %x[git check-ref-format --normalize "refs/heads/#{@name}"].chomp 163 | die "invalid branch name: #{@name}" unless $?.success? 164 | 165 | if base 166 | base = $prefix + base 167 | system(*%w[git rev-parse --quiet --verify], "#{base}^{commit}", :out => File::NULL) 168 | die "no such commit: #{base}" unless $?.success? 169 | else 170 | base = $prefix + 'master' 171 | end 172 | 173 | @int = @ref.gsub(%r{^refs/heads/}, 'refs/int/') 174 | 175 | system(*%w[git update-ref], @ref, "#{base}^{commit}", NULL_SHA1) 176 | write_instructions("base #{base.delete_prefix($prefix)}\n") 177 | system(*%w[git checkout], @name) 178 | puts "Integration branch #{@name} created." 179 | end 180 | 181 | def generate(base = nil) 182 | if @name 183 | @ref = %x[git rev-parse --symbolic-full-name "refs/heads/#{@name}"].chomp 184 | die "no such branch: #{@name}" unless $?.success? 185 | else 186 | @ref = %x[git symbolic-ref HEAD].chomp 187 | die "HEAD is detached, could not figure out which integration branch to use" unless $?.success? 188 | @name = @ref.gsub(%r{^refs/heads/}, '') 189 | end 190 | 191 | if base 192 | system(*%w[git rev-parse --quiet --verify], "#{base}^{commit}", :out => File::NULL) 193 | die "no such commit: #{base}" unless $?.success? 194 | else 195 | base = 'master' 196 | end 197 | 198 | @int = @ref.gsub(%r{^refs/heads/}, 'refs/int/') 199 | 200 | series = [] 201 | 202 | series << "base #{base}" 203 | 204 | IO.popen(%w[git log --no-decorate --format=%H%n%B -z --reverse --first-parent] + ["^#{base}", @name]) do |io| 205 | io.each("\0") do |l| 206 | commit, summary, body = l.chomp("\0").split("\n", 3) 207 | body.lstrip! 208 | other = %x[git rev-parse -q --verify "#{commit}^2"].chomp 209 | if not other.empty? 210 | body.gsub!(/\n?(\* .*:.*\n.*)/m, '') 211 | series << parse_merge(other, commit, summary, body) 212 | end 213 | end 214 | end 215 | 216 | write_instructions(series.join("\n") + "\n") 217 | 218 | puts "Integration branch #{@name} generated." 219 | end 220 | 221 | def read_instructions 222 | %x[git cat-file blob #{@int}:instructions].chomp 223 | end 224 | 225 | def write_instructions(content) 226 | insn_blob = insn_tree = insn_commit = nil 227 | 228 | parent = %x[git rev-parse --quiet --verify #{@int}].chomp 229 | parent_tree = %x[git rev-parse --quiet --verify #{@int}^{tree}].chomp 230 | 231 | parent = nil if parent.empty? 232 | 233 | IO.popen(%[git hash-object -w --stdin], 'r+') do |io| 234 | io.write(content) 235 | io.close_write 236 | insn_blob = io.read.chomp 237 | end 238 | die "Failed to write instruction sheet blob object" unless $?.success? 239 | 240 | IO.popen(%[git mktree], 'r+') do |io| 241 | io.printf "100644 blob %s\t%s\n", insn_blob, 'instructions' 242 | io.close_write 243 | insn_tree = io.read.chomp 244 | end 245 | die "Failed to write instruction sheet tree object" unless $?.success? 246 | 247 | # If there isn't anything to commit, stop now. 248 | return if insn_tree == parent_tree 249 | 250 | op = parent ? 'Update' : 'Create' 251 | opts = parent ? ['-p', parent] : [] 252 | opts << insn_tree 253 | IO.popen(%w[git commit-tree] + opts, 'r+') do |io| 254 | io.write("#{op} integration branch #{@int}") 255 | io.close_write 256 | insn_commit = io.read.chomp 257 | end 258 | die "Failed to write instruction sheet commit" unless $?.success? 259 | 260 | system(*%w[git update-ref], @int, insn_commit, parent || NULL_SHA1) 261 | die "Failed to update instruction sheet reference" unless $?.success? 262 | end 263 | 264 | end 265 | 266 | class Integration 267 | 268 | attr_reader :commands 269 | 270 | class Stop < Exception 271 | end 272 | 273 | class Pause < Exception 274 | end 275 | 276 | @@map = { '.' => :cmd_dot } 277 | 278 | def initialize(obj) 279 | self.load(obj) 280 | end 281 | 282 | def load(obj) 283 | cmd, args = nil 284 | msg = "" 285 | cmds = [] 286 | obj.each_line do |l| 287 | l.chomp! 288 | case l 289 | when '' 290 | when /^\s(.*)$/ 291 | msg << $1 292 | when /(\S+) ?(.*)$/ 293 | cmds << [cmd, args, msg] if cmd 294 | cmd = $1 295 | args = $2.split(' ') 296 | msg = "" 297 | end 298 | end 299 | cmds << [cmd, args, msg] if cmd 300 | @commands = cmds 301 | end 302 | 303 | def self.run(obj) 304 | self.new(obj).run 305 | end 306 | 307 | def self.start(branch, into_name, inst) 308 | require_clean_work_tree('integrate', "Please commit or stash them.") 309 | 310 | orig_head = %x[git rev-parse --quiet --verify "#{branch}^{commit}"].chomp 311 | system(*%w[git update-ref ORIG_HEAD], orig_head) 312 | 313 | system(*%w[git checkout --quiet], "#{branch}^0") 314 | die "could not detach HEAD" unless $?.success? 315 | 316 | FileUtils.mkdir_p($state_dir) 317 | 318 | File.write($head_file, branch) 319 | commit = %x[git rev-parse --quiet --verify #{branch}].chomp 320 | File.write($start_file, commit) 321 | 322 | $branch.into_name = into_name 323 | File.write($into_file, into_name) 324 | 325 | File.write($autocontinue_file, $autocontinue) 326 | File.write($prefix_file, $prefix) 327 | 328 | File.write($insns, inst) 329 | self.run(inst) 330 | end 331 | 332 | def run 333 | begin 334 | while cmd = @commands.first 335 | finalize_command(*cmd) 336 | @commands.shift 337 | end 338 | rescue Integration::Stop => e 339 | stop(e.message) 340 | rescue Integration::Pause => e 341 | @commands.shift 342 | stop(e.message) 343 | else 344 | finish 345 | end 346 | end 347 | 348 | def finalize_command(cmd, args, message) 349 | begin 350 | fun = @@map[cmd] || "cmd_#{cmd}".to_sym 351 | send(fun, message, *args) 352 | rescue NoMethodError 353 | raise Integration::Stop, "Unknown command: #{cmd}" 354 | end 355 | end 356 | 357 | def finish 358 | msg = "reintegrate: rebuilt #{$branch.name}" 359 | system(*%w[git update-ref], '-m', msg, $branch.into_ref, 'HEAD', File.read($start_file)) 360 | system(*%w[git symbolic-ref], '-m', msg, 'HEAD', $branch.into_ref) 361 | FileUtils.rm_rf($state_dir) 362 | system(*%w[git gc --auto]) 363 | if $branch.name == $branch.into_name 364 | puts "Successfully re-integrated #{$branch.name}." 365 | else 366 | puts "Successfully applied #{$branch.name} into #{$branch.into_name}." 367 | end 368 | end 369 | 370 | def stop(msg = nil) 371 | File.open($insns, 'w') do |f| 372 | @commands.each do |cmd, args, msg| 373 | str = "%s %s\n" % [cmd, args.join(' ')] 374 | str += "%s\n" % msg if msg and not msg.empty? 375 | f.write(str) 376 | end 377 | end 378 | 379 | File.write($merged_file, $merged.join("\n")) 380 | 381 | $stderr.puts(msg) if msg and ! msg.empty? 382 | $stderr.puts < ':' }, *%w[git commit --amend -a]) 478 | raise Integration::Stop, '' unless $?.success? 479 | end 480 | 481 | def cmd_commit(message, *args) 482 | puts "Emtpy commit" 483 | 484 | system(*%w[git commit --allow-empty -m], message) 485 | raise Integration::Stop, '' unless $?.success? 486 | end 487 | 488 | def cmd_pause(message, *args) 489 | raise Integration::Pause, (message || 'Pause') 490 | end 491 | 492 | def cmd_dot(message, *args) 493 | end 494 | 495 | def require_clean_work_tree(action = nil, msg = nil, quiet = false) 496 | system(*%w[git update-index -q --ignore-submodules --refresh]) 497 | errors = [] 498 | 499 | system(*%w[git diff-files --quiet --ignore-submodules]) 500 | errors << "Cannot #{action}: You have unstaged changes." unless $?.success? 501 | 502 | system(*%w[git diff-index --cached --quiet --ignore-submodules HEAD --]) 503 | if not $?.success? 504 | if errors.empty? 505 | errors << "Cannot #{action}: Your index contains uncommitted changes." 506 | else 507 | errors << "Additionally, your index contains uncommitted changes." 508 | end 509 | end 510 | 511 | if not errors.empty? and not quiet 512 | errors.each do |e| 513 | $stderr.puts(e) 514 | end 515 | $stderr.puts(msg) if msg 516 | exit 1 517 | end 518 | 519 | return errors.empty? 520 | end 521 | 522 | def do_rebuild 523 | inst = $branch.read_instructions 524 | die "Failed to read instruction list for branch #{$branch.name}" unless $?.success? 525 | 526 | Integration.start($branch.name, $branch.name, inst) 527 | end 528 | 529 | def get_head_file 530 | die "no integration in progress" unless test('f', $head_file) 531 | branch_name = File.read($head_file) 532 | branch = Branch.new(branch_name) 533 | branch.get 534 | branch.into_name = File.read($into_file) 535 | $autocontinue = File.read($autocontinue_file) == 'true' 536 | $prefix = File.read($prefix_file) 537 | return branch 538 | end 539 | 540 | def do_continue 541 | $branch = get_head_file 542 | 543 | if File.exists?("#{$git_dir}/MERGE_HEAD") 544 | # We are being called to continue an existing operation, 545 | # without the user having manually committed the result of 546 | # resolving conflicts. 547 | system(*%w[git update-index --ignore-submodules --refresh]) && 548 | system(*%w[git diff-files --quiet --ignore-submodules]) || 549 | die("You must edit all merge conflicts and then mark them as resolved using git add") 550 | 551 | system(*%w[git commit --quiet --no-edit]) 552 | die "merge_head" unless $?.success? 553 | end 554 | 555 | $merged = File.read($merged_file).split("\n") 556 | 557 | File.open($insns) do |f| 558 | Integration.run(f) 559 | end 560 | end 561 | 562 | def do_abort 563 | $branch = get_head_file 564 | 565 | system(*%w[git symbolic-ref HEAD], $branch.into_ref) && 566 | system(*%w[git reset --hard], $branch.into_ref) && 567 | FileUtils.rm_rf($state_dir) 568 | end 569 | 570 | def do_apply 571 | head = %x[git rev-parse --symbolic --abbrev-ref HEAD].chomp 572 | 573 | inst = $branch.read_instructions 574 | die "Failed to read instruction list for branch #{$branch.name}" unless $?.success? 575 | 576 | inst = inst.lines.reject do |line| 577 | next true if line =~ /^base / 578 | if line =~ /^merge (\S+)/ 579 | system(*%W[git merge-base --is-ancestor #{$1} HEAD]) 580 | next true if $?.success? 581 | end 582 | false 583 | end.join 584 | 585 | Integration.start(head, head, inst) 586 | end 587 | 588 | def status_merge(branch_to_merge = nil, *args) 589 | if not branch_to_merge 590 | $stderr.puts "no branch specified with 'merge' command" 591 | return 592 | end 593 | $status_base ||= $prefix + 'master' 594 | 595 | branch_to_merge = $prefix + branch_to_merge 596 | 597 | if ! system(*%w[git rev-parse --verify --quiet], "#{branch_to_merge}^{commit}", :out => File::NULL) 598 | state = "." 599 | verbose_state = "branch not found" 600 | elsif system(*%w[git merge-base --is-ancestor], branch_to_merge, $status_base) 601 | state = "+" 602 | verbose_state = "merged to #{$status_base}" 603 | elsif system(*%w[git merge-base --is-ancestor], branch_to_merge, $branch.name) 604 | state = "*" 605 | verbose_state = "up-to-date" 606 | else 607 | state = "-" 608 | verbose_state = "branch changed" 609 | end 610 | 611 | printf("%s %-*s(%s)", state, $longest_branch, branch_to_merge, verbose_state) 612 | puts $message.gsub(/^./, ' \&') if $message 613 | puts 614 | 615 | log = %x[git --no-pager log --oneline --cherry "#{$status_base}...#{branch_to_merge}" -- 2> /dev/null].chomp 616 | print(log.gsub(/^/, ' ') + "\n\n") unless log.empty? 617 | end 618 | 619 | def status_dot(*args) 620 | puts ". %s\n" % args.join(' ') 621 | puts $message.gsub(/^./, ' \&') if $message 622 | end 623 | 624 | def do_status 625 | inst = $branch.read_instructions 626 | die "Failed to read instruction list for branch #{$branch.name}" unless $?.success? 627 | 628 | int = Integration.new(inst) 629 | cmds = int.commands 630 | 631 | $longest_branch = cmds.map do |cmd, args, msg| 632 | next 0 if cmd != 'merge' 633 | ($prefix + args.first).size 634 | end.max 635 | 636 | cmds.each do |cmd, args, msg| 637 | case cmd 638 | when 'base' 639 | $status_base = $prefix + args.first 640 | when 'merge' 641 | status_merge(*args) 642 | when '.' 643 | status_dot(*args) 644 | else 645 | $stderr.puts "unhandled command: #{cmd} #{args}" 646 | end 647 | end 648 | end 649 | 650 | opts = ParseOpt.new 651 | opts.usage = 'git reintegrate' 652 | 653 | opts.on('c', 'create', 'create a new integration branch') do |v| 654 | $create = true 655 | $need_rebuild = true 656 | end 657 | 658 | opts.on('e', 'edit', 'edit the instruction sheet for a branch') do |v| 659 | $edit = true 660 | $need_rebuild = true 661 | end 662 | 663 | opts.on('r', 'rebuild', 'rebuild an integration branch') do |v| 664 | $rebuild = v 665 | end 666 | 667 | opts.on(nil, 'continue', 'continue an in-progress rebuild') do |v| 668 | $actions << :continue 669 | end 670 | 671 | opts.on(nil, 'abort', 'abort an in-progress rebuild') do |v| 672 | $actions << :abort 673 | end 674 | 675 | opts.on('g', 'generate', 'generate instruction sheet') do |v| 676 | $generate = true 677 | end 678 | 679 | opts.on('a', 'add', 'add a branch to merge in the instruction sheet') do |v| 680 | $branches_to_add << v 681 | $need_rebuild = true 682 | end 683 | 684 | opts.on(nil, 'autocontinue', 'continue automatically on merge conflicts if possible') do |v| 685 | $autocontinue = v 686 | end 687 | 688 | opts.on(nil, 'cat', 'show the instructions of an integration branch') do |v| 689 | $cat = v 690 | end 691 | 692 | opts.on('s', 'status', 'shows the status of all the dependencies of an integration branch') do |v| 693 | $status = true 694 | end 695 | 696 | opts.on('l', 'list', 'list integration branches') do |v| 697 | $list = true 698 | end 699 | 700 | opts.on('d', 'delete', 'delete an integration branch') do |v| 701 | $delete = true 702 | end 703 | 704 | opts.on(nil, 'apply', 'apply an integration branch on the current branch') do |v| 705 | $apply = true 706 | end 707 | 708 | opts.on(nil, 'prefix', '') do |v| 709 | $prefix = v 710 | end 711 | 712 | %x[git config --bool --get integration.autocontinue].chomp == "true" && 713 | $autocontinue = true 714 | 715 | opts.parse 716 | 717 | $git_cdup = %x[git rev-parse --show-cdup].chomp 718 | die "You need to run this command from the toplevel of the working tree." unless $git_cdup.empty? 719 | 720 | $prefix = %x[git config --get integration.prefix].chomp unless $prefix 721 | 722 | $git_dir = %x[git rev-parse --git-dir].chomp 723 | 724 | $state_dir = "#{$git_dir}/integration" 725 | $start_file = "#{$state_dir}/start-point" 726 | $head_file = "#{$state_dir}/head-name" 727 | $merged_file = "#{$state_dir}/merged" 728 | $prefix_file = "#{$state_dir}/prefix" 729 | $insns = "#{$state_dir}/instructions" 730 | $into_file = "#{$state_dir}/into" 731 | $autocontinue_file = "#{$state_dir}/autocontinue" 732 | 733 | case $actions.first 734 | when :continue 735 | do_continue 736 | when :abort 737 | do_abort 738 | end 739 | 740 | if $list 741 | IO.popen(%w[git for-each-ref refs/int/]).each do |line| 742 | if line =~ %r{.*\trefs/int/(.*)} 743 | puts $1 744 | end 745 | end 746 | exit 747 | end 748 | 749 | if $delete 750 | name = ARGV[0] 751 | msg = 'reintegrate: delete branch' 752 | system(*%W[git update-ref -d refs/int/#{name}] + ['-m', msg]) 753 | exit 754 | end 755 | 756 | unless $cat || $status 757 | die "integration already in progress" if test('f', $head_file) 758 | end 759 | 760 | $branches_to_add.each do |branch| 761 | branch = $prefix + branch 762 | system(*%w[git rev-parse --quiet --verify], "#{branch}^{commit}", :out => File::NULL) 763 | die "not a valid commit: #{branch}" unless $?.success? 764 | end 765 | 766 | $branch = Branch.new(ARGV[0]) 767 | if $create 768 | $branch.create(ARGV[1]) 769 | elsif $generate 770 | $branch.generate(ARGV[1]) 771 | else 772 | $branch.get 773 | end 774 | 775 | if $edit || ! $branches_to_add.empty? 776 | do_edit 777 | end 778 | 779 | if $cat 780 | puts $branch.read_instructions 781 | end 782 | 783 | if $rebuild == nil && $need_rebuild == true 784 | %x[git config --bool --get integration.autorebuild].chomp == "true" && 785 | $rebuild = true 786 | end 787 | 788 | if $rebuild 789 | do_rebuild 790 | end 791 | 792 | if $apply 793 | do_apply 794 | end 795 | 796 | if $status 797 | do_status 798 | end 799 | -------------------------------------------------------------------------------- /t/sharness.sh: -------------------------------------------------------------------------------- 1 | # Sharness test framework. 2 | # 3 | # Copyright (c) 2011-2012 Mathias Lafeldt 4 | # Copyright (c) 2005-2012 Git project 5 | # Copyright (c) 2005-2012 Junio C Hamano 6 | # Copyright (c) 2019-2023 Felipe Contreras 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see http://www.gnu.org/licenses/ . 20 | 21 | if test -n "${ZSH_VERSION-}" 22 | then 23 | emulate sh 24 | ARGZERO="$ZSH_ARGZERO" 25 | else 26 | ARGZERO="$0" 27 | fi 28 | 29 | # Public: Current version of Sharness. 30 | SHARNESS_VERSION="1.2.0" 31 | export SHARNESS_VERSION 32 | 33 | : "${SHARNESS_TEST_EXTENSION:=t}" 34 | # Public: The file extension for tests. By default, it is set to "t". 35 | export SHARNESS_TEST_EXTENSION 36 | 37 | : "${SHARNESS_TEST_DIRECTORY:=$(dirname "$ARGZERO")}" 38 | # ensure that SHARNESS_TEST_DIRECTORY is an absolute path so that it 39 | # is valid even if the current working directory is changed 40 | SHARNESS_TEST_DIRECTORY=$(cd "$SHARNESS_TEST_DIRECTORY" && pwd) || exit 1 41 | # Public: Root directory containing tests. Tests can override this variable, 42 | # e.g. for testing Sharness itself. 43 | export SHARNESS_TEST_DIRECTORY 44 | 45 | # shellcheck disable=SC3028 46 | : "${SHARNESS_TEST_SRCDIR:=$(cd "$(dirname "${BASH_SOURCE-$0}")" && pwd)}" 47 | # Public: Source directory of test code and sharness library. 48 | # This directory may be different from the directory in which tests are 49 | # being run. 50 | export SHARNESS_TEST_SRCDIR 51 | 52 | : "${SHARNESS_TEST_OUTDIR:=$SHARNESS_TEST_DIRECTORY}" 53 | # Public: Directory where the output of the tests should be stored (i.e. 54 | # trash directories). 55 | export SHARNESS_TEST_OUTDIR 56 | 57 | # Reset TERM to original terminal if found, otherwise save original TERM 58 | [ -z "$SHARNESS_ORIG_TERM" ] && 59 | SHARNESS_ORIG_TERM="$TERM" || 60 | TERM="$SHARNESS_ORIG_TERM" 61 | # Public: The unsanitized TERM under which sharness is originally run 62 | export SHARNESS_ORIG_TERM 63 | 64 | # Export SHELL_PATH 65 | : "${SHELL_PATH:=/bin/sh}" 66 | export SHELL_PATH 67 | 68 | # if --tee was passed, write the output not only to the terminal, but 69 | # additionally to the file test-results/$BASENAME.out, too. 70 | case "$SHARNESS_TEST_TEE_STARTED, $* " in 71 | done,*) 72 | # do not redirect again 73 | ;; 74 | *' --tee '*|*' --verbose-log '*) 75 | mkdir -p "$SHARNESS_TEST_OUTDIR/test-results" 76 | BASE="$SHARNESS_TEST_OUTDIR/test-results/$(basename "$ARGZERO" ".$SHARNESS_TEST_EXTENSION")" 77 | 78 | # Make this filename available to the sub-process in case it is using 79 | # --verbose-log. 80 | SHARNESS_TEST_TEE_OUTPUT_FILE="$BASE.out" 81 | export SHARNESS_TEST_TEE_OUTPUT_FILE 82 | 83 | # Truncate before calling "tee -a" to get rid of the results 84 | # from any previous runs. 85 | : >"$SHARNESS_TEST_TEE_OUTPUT_FILE" 86 | 87 | (SHARNESS_TEST_TEE_STARTED="done" ${SHELL_PATH} "$ARGZERO" "$@" 2>&1; 88 | echo $? >"$BASE.exit") | tee -a "$SHARNESS_TEST_TEE_OUTPUT_FILE" 89 | test "$(cat "$BASE.exit")" = 0 90 | exit 91 | ;; 92 | esac 93 | 94 | # For repeatability, reset the environment to a known state. 95 | # TERM is sanitized below, after saving color control sequences. 96 | LANG=C 97 | LC_ALL=C 98 | PAGER="cat" 99 | TZ=UTC 100 | EDITOR=: 101 | export LANG LC_ALL PAGER TZ EDITOR 102 | unset VISUAL CDPATH GREP_OPTIONS 103 | 104 | [ "x$TERM" != "xdumb" ] && ( 105 | [ -t 1 ] && 106 | tput bold >/dev/null 2>&1 && 107 | tput setaf 1 >/dev/null 2>&1 && 108 | tput sgr0 >/dev/null 2>&1 109 | ) && 110 | color=t 111 | 112 | while test "$#" -ne 0; do 113 | case "$1" in 114 | -d|--d|--de|--deb|--debu|--debug) 115 | debug=t; shift ;; 116 | -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) 117 | immediate=t; shift ;; 118 | -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) 119 | TEST_LONG=t; export TEST_LONG; shift ;; 120 | --in|--int|--inte|--inter|--intera|--interac|--interact|--interacti|--interactiv|--interactive|--interactive-|--interactive-t|--interactive-te|--interactive-tes|--interactive-test|--interactive-tests): 121 | TEST_INTERACTIVE=t; export TEST_INTERACTIVE; verbose=t; shift ;; 122 | -h|--h|--he|--hel|--help) 123 | help=t; shift ;; 124 | -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) 125 | verbose=t; shift ;; 126 | -q|--q|--qu|--qui|--quie|--quiet) 127 | # Ignore --quiet under a TAP::Harness. Saying how many tests 128 | # passed without the ok/not ok details is always an error. 129 | test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; 130 | --chain-lint) 131 | chain_lint=t; shift ;; 132 | --no-chain-lint) 133 | chain_lint=; shift ;; 134 | --no-color) 135 | color=; shift ;; 136 | --tee) 137 | shift ;; # was handled already 138 | --root=*) 139 | root=$(expr "z$1" : 'z[^=]*=\(.*\)') 140 | shift ;; 141 | -x) 142 | trace=t 143 | shift ;; 144 | --verbose-log) 145 | verbose_log=t 146 | shift ;; 147 | *) 148 | echo "error: unknown test option '$1'" >&2; exit 1 ;; 149 | esac 150 | done 151 | 152 | if test -n "$color"; then 153 | # Save the color control sequences now rather than run tput 154 | # each time say_color() is called. This is done for two 155 | # reasons: 156 | # * TERM will be changed to dumb 157 | # * HOME will be changed to a temporary directory and tput 158 | # might need to read ~/.terminfo from the original HOME 159 | # directory to get the control sequences 160 | # Note: This approach assumes the control sequences don't end 161 | # in a newline for any terminal of interest (command 162 | # substitutions strip trailing newlines). Given that most 163 | # (all?) terminals in common use are related to ECMA-48, this 164 | # shouldn't be a problem. 165 | say_color_error=$(tput bold; tput setaf 1) # bold red 166 | say_color_skip=$(tput setaf 4) # blue 167 | say_color_warn=$(tput setaf 3) # brown/yellow 168 | say_color_pass=$(tput setaf 2) # green 169 | say_color_info=$(tput setaf 6) # cyan 170 | say_color_reset=$(tput sgr0) 171 | say_color_raw="" # no formatting for normal text 172 | say_color() { 173 | test -z "$1" && test -n "$quiet" && return 174 | case "$1" in 175 | error) say_color_color=$say_color_error ;; 176 | skip) say_color_color=$say_color_skip ;; 177 | warn) say_color_color=$say_color_warn ;; 178 | pass) say_color_color=$say_color_pass ;; 179 | info) say_color_color=$say_color_info ;; 180 | *) say_color_color=$say_color_raw ;; 181 | esac 182 | shift 183 | printf '%s%s%s\n' "$say_color_color" "$*" "$say_color_reset" 184 | } 185 | else 186 | say_color() { 187 | test -z "$1" && test -n "$quiet" && return 188 | shift 189 | printf '%s\n' "$*" 190 | } 191 | fi 192 | 193 | : "${test_untraceable:=}" 194 | # Public: When set to a non-empty value, the current test will not be 195 | # traced, unless it's run with a Bash version supporting 196 | # BASH_XTRACEFD, i.e. v4.1 or later. 197 | export test_untraceable 198 | 199 | if test -n "$trace" && test -n "$test_untraceable" 200 | then 201 | # '-x' tracing requested, but this test script can't be reliably 202 | # traced, unless it is run with a Bash version supporting 203 | # BASH_XTRACEFD (introduced in Bash v4.1). 204 | # 205 | # Perform this version check _after_ the test script was 206 | # potentially re-executed with $TEST_SHELL_PATH for '--tee' or 207 | # '--verbose-log', so the right shell is checked and the 208 | # warning is issued only once. 209 | if test -n "$BASH_VERSION" && eval ' 210 | test ${BASH_VERSINFO[0]} -gt 4 || { 211 | test ${BASH_VERSINFO[0]} -eq 4 && 212 | test ${BASH_VERSINFO[1]} -ge 1 213 | } 214 | ' 215 | then 216 | : Executed by a Bash version supporting BASH_XTRACEFD. Good. 217 | else 218 | echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" 219 | trace= 220 | fi 221 | fi 222 | if test -n "$trace" && test -z "$verbose_log" 223 | then 224 | verbose=t 225 | fi 226 | 227 | TERM=dumb 228 | export TERM 229 | 230 | error() { 231 | say_color error "error: $*" 232 | EXIT_OK=t 233 | exit 1 234 | } 235 | 236 | say() { 237 | say_color info "$*" 238 | } 239 | 240 | test -n "${test_description:-}" || error "Test script did not set test_description." 241 | 242 | if test "$help" = "t"; then 243 | echo "$test_description" 244 | exit 0 245 | fi 246 | 247 | exec 5>&1 248 | exec 6<&0 249 | if test "$verbose_log" = "t" 250 | then 251 | exec 3>>"$SHARNESS_TEST_TEE_OUTPUT_FILE" 4>&3 252 | elif test "$verbose" = "t" 253 | then 254 | exec 4>&2 3>&1 255 | else 256 | exec 4>/dev/null 3>/dev/null 257 | fi 258 | 259 | # Send any "-x" output directly to stderr to avoid polluting tests 260 | # which capture stderr. We can do this unconditionally since it 261 | # has no effect if tracing isn't turned on. 262 | # 263 | # Note that this sets up the trace fd as soon as we assign the variable, so it 264 | # must come after the creation of descriptor 4 above. Likewise, we must never 265 | # unset this, as it has the side effect of closing descriptor 4, which we 266 | # use to show verbose tests to the user. 267 | # 268 | # Note also that we don't need or want to export it. The tracing is local to 269 | # this shell, and we would not want to influence any shells we exec. 270 | BASH_XTRACEFD=4 271 | 272 | # Public: The current test number, starting at 0. 273 | SHARNESS_TEST_NB=0 274 | export SHARNESS_TEST_NB 275 | 276 | die() { 277 | code=$? 278 | if test -n "$EXIT_OK"; then 279 | exit $code 280 | else 281 | echo >&5 "FATAL: Unexpected exit with code $code" 282 | exit 1 283 | fi 284 | } 285 | 286 | EXIT_OK= 287 | trap 'die' EXIT 288 | 289 | test_prereq= 290 | missing_prereq= 291 | 292 | test_failure=0 293 | test_fixed=0 294 | test_broken=0 295 | test_success=0 296 | 297 | if test -e "$SHARNESS_TEST_SRCDIR/lib-sharness/functions.sh" 298 | then 299 | . "$SHARNESS_TEST_SRCDIR/lib-sharness/functions.sh" 300 | fi 301 | 302 | # You are not expected to call test_ok_ and test_failure_ directly, use 303 | # the text_expect_* functions instead. 304 | 305 | test_ok_() { 306 | test_success=$((test_success + 1)) 307 | say_color "" "ok $SHARNESS_TEST_NB - $*" 308 | } 309 | 310 | test_failure_() { 311 | test_failure=$((test_failure + 1)) 312 | say_color error "not ok $SHARNESS_TEST_NB - $1" 313 | shift 314 | echo "$@" | sed -e 's/^/# /' 315 | test "$immediate" = "" || { EXIT_OK=t; exit 1; } 316 | } 317 | 318 | test_known_broken_ok_() { 319 | test_fixed=$((test_fixed + 1)) 320 | say_color error "ok $SHARNESS_TEST_NB - $* # TODO known breakage vanished" 321 | } 322 | 323 | test_known_broken_failure_() { 324 | test_broken=$((test_broken + 1)) 325 | say_color warn "not ok $SHARNESS_TEST_NB - $* # TODO known breakage" 326 | } 327 | 328 | want_trace () { 329 | test "$trace" = t && { 330 | test "$verbose" = t || test "$verbose_log" = t 331 | } 332 | } 333 | 334 | # This is a separate function because some tests use 335 | # "return" to end a test_expect_success block early 336 | # (and we want to make sure we run any cleanup like 337 | # "set +x"). 338 | test_eval_inner_ () { 339 | # Do not add anything extra (including LF) after '$*' 340 | eval " 341 | want_trace && set -x 342 | $*" 343 | } 344 | 345 | test_eval_x_ () { 346 | # If "-x" tracing is in effect, then we want to avoid polluting stderr 347 | # with non-test commands. But once in "set -x" mode, we cannot prevent 348 | # the shell from printing the "set +x" to turn it off (nor the saving 349 | # of $? before that). But we can make sure that the output goes to 350 | # /dev/null. 351 | # 352 | # There are a few subtleties here: 353 | # 354 | # - we have to redirect descriptor 4 in addition to 2, to cover 355 | # BASH_XTRACEFD 356 | # 357 | # - the actual eval has to come before the redirection block (since 358 | # it needs to see descriptor 4 to set up its stderr) 359 | # 360 | # - likewise, any error message we print must be outside the block to 361 | # access descriptor 4 362 | # 363 | # - checking $? has to come immediately after the eval, but it must 364 | # be _inside_ the block to avoid polluting the "set -x" output 365 | # 366 | 367 | test_eval_inner_ "$@" &3 2>&4 368 | { 369 | test_eval_ret_=$? 370 | if want_trace 371 | then 372 | set +x 373 | fi 374 | } 2>/dev/null 4>&2 375 | 376 | if test "$test_eval_ret_" != 0 && want_trace 377 | then 378 | say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" 379 | fi 380 | return $test_eval_ret_ 381 | } 382 | 383 | test_eval_() { 384 | case ",$test_prereq," in 385 | *,INTERACTIVE,*) 386 | eval "$*" 387 | ;; 388 | *) 389 | test_eval_x_ "$@" 390 | ;; 391 | esac 392 | } 393 | 394 | test_run_() { 395 | test_cleanup=: 396 | expecting_failure=$2 397 | test_eval_ "$1" 398 | eval_ret=$? 399 | 400 | if test "$chain_lint" = "t"; then 401 | # turn off tracing for this test-eval, as it simply creates 402 | # confusing noise in the "-x" output 403 | trace_tmp=$trace 404 | trace= 405 | # 117 is magic because it is unlikely to match the exit 406 | # code of other programs 407 | test_eval_ "(exit 117) && $1" 408 | if test "$?" != 117; then 409 | error "bug in the test script: broken &&-chain: $1" 410 | fi 411 | trace=$trace_tmp 412 | fi 413 | 414 | if test -z "$immediate" || test $eval_ret = 0 || 415 | test -n "$expecting_failure" && test "$test_cleanup" != ":" 416 | then 417 | test_eval_ "$test_cleanup" 418 | fi 419 | if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then 420 | echo "" 421 | fi 422 | return "$eval_ret" 423 | } 424 | 425 | test_skip_() { 426 | SHARNESS_TEST_NB=$((SHARNESS_TEST_NB + 1)) 427 | to_skip= 428 | for skp in $SKIP_TESTS; do 429 | # shellcheck disable=SC2254 430 | case $this_test.$SHARNESS_TEST_NB in 431 | $skp) 432 | to_skip=t 433 | break 434 | esac 435 | done 436 | if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq"; then 437 | to_skip=t 438 | fi 439 | case "$to_skip" in 440 | t) 441 | of_prereq= 442 | if test "$missing_prereq" != "$test_prereq"; then 443 | of_prereq=" of $test_prereq" 444 | fi 445 | 446 | say_color skip >&3 "skipping test: $*" 447 | say_color skip "ok $SHARNESS_TEST_NB # skip $1 (missing $missing_prereq${of_prereq})" 448 | : true 449 | ;; 450 | *) 451 | false 452 | ;; 453 | esac 454 | } 455 | 456 | remove_trash_() { 457 | test -d "$remove_trash" && ( 458 | cd "$(dirname "$remove_trash")" && 459 | rm -rf "$(basename "$remove_trash")" 460 | ) 461 | } 462 | 463 | # Public: Run test commands and expect them to succeed. 464 | # 465 | # When the test passed, an "ok" message is printed and the number of successful 466 | # tests is incremented. When it failed, a "not ok" message is printed and the 467 | # number of failed tests is incremented. 468 | # 469 | # With --immediate, exit test immediately upon the first failed test. 470 | # 471 | # Usually takes two arguments: 472 | # $1 - Test description 473 | # $2 - Commands to be executed. 474 | # 475 | # With three arguments, the first will be taken to be a prerequisite: 476 | # $1 - Comma-separated list of test prerequisites. The test will be skipped if 477 | # not all of the given prerequisites are set. To negate a prerequisite, 478 | # put a "!" in front of it. 479 | # $2 - Test description 480 | # $3 - Commands to be executed. 481 | # 482 | # Examples 483 | # 484 | # test_expect_success \ 485 | # 'git-write-tree should be able to write an empty tree.' \ 486 | # 'tree=$(git-write-tree)' 487 | # 488 | # # Test depending on one prerequisite. 489 | # test_expect_success TTY 'git --paginate rev-list uses a pager' \ 490 | # ' ... ' 491 | # 492 | # # Multiple prerequisites are separated by a comma. 493 | # test_expect_success PERL,PYTHON 'yo dawg' \ 494 | # ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" ' 495 | # 496 | # Returns nothing. 497 | test_expect_success() { 498 | test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= 499 | test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_success" 500 | export test_prereq 501 | if ! test_skip_ "$@"; then 502 | say >&3 "expecting success: $2" 503 | if test_run_ "$2"; then 504 | test_ok_ "$1" 505 | else 506 | test_failure_ "$@" 507 | fi 508 | fi 509 | echo >&3 "" 510 | } 511 | 512 | # Public: Run test commands and expect them to fail. Used to demonstrate a known 513 | # breakage. 514 | # 515 | # This is NOT the opposite of test_expect_success, but rather used to mark a 516 | # test that demonstrates a known breakage. 517 | # 518 | # When the test passed, an "ok" message is printed and the number of fixed tests 519 | # is incremented. When it failed, a "not ok" message is printed and the number 520 | # of tests still broken is incremented. 521 | # 522 | # Failures from these tests won't cause --immediate to stop. 523 | # 524 | # Usually takes two arguments: 525 | # $1 - Test description 526 | # $2 - Commands to be executed. 527 | # 528 | # With three arguments, the first will be taken to be a prerequisite: 529 | # $1 - Comma-separated list of test prerequisites. The test will be skipped if 530 | # not all of the given prerequisites are set. To negate a prerequisite, 531 | # put a "!" in front of it. 532 | # $2 - Test description 533 | # $3 - Commands to be executed. 534 | # 535 | # Returns nothing. 536 | test_expect_failure() { 537 | test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= 538 | test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_failure" 539 | export test_prereq 540 | if ! test_skip_ "$@"; then 541 | say >&3 "checking known breakage: $2" 542 | if test_run_ "$2" expecting_failure; then 543 | test_known_broken_ok_ "$1" 544 | else 545 | test_known_broken_failure_ "$1" 546 | fi 547 | fi 548 | echo >&3 "" 549 | } 550 | 551 | # Public: Run test commands and expect anything from them. Used when a 552 | # test is not stable or not finished for some reason. 553 | # 554 | # When the test passed, an "ok" message is printed, but the number of 555 | # fixed tests is not incremented. 556 | # 557 | # When it failed, a "not ok ... # TODO known breakage" message is 558 | # printed, and the number of tests still broken is incremented. 559 | # 560 | # Failures from these tests won't cause --immediate to stop. 561 | # 562 | # Usually takes two arguments: 563 | # $1 - Test description 564 | # $2 - Commands to be executed. 565 | # 566 | # With three arguments, the first will be taken to be a prerequisite: 567 | # $1 - Comma-separated list of test prerequisites. The test will be skipped if 568 | # not all of the given prerequisites are set. To negate a prerequisite, 569 | # put a "!" in front of it. 570 | # $2 - Test description 571 | # $3 - Commands to be executed. 572 | # 573 | # Returns nothing. 574 | test_expect_unstable() { 575 | test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= 576 | test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_unstable" 577 | export test_prereq 578 | if ! test_skip_ "$@"; then 579 | say >&3 "checking unstable test: $2" 580 | if test_run_ "$2" unstable; then 581 | test_ok_ "$1" 582 | else 583 | test_known_broken_failure_ "$1" 584 | fi 585 | fi 586 | echo >&3 "" 587 | } 588 | 589 | # Public: Summarize test results and exit with an appropriate error code. 590 | # 591 | # Must be called at the end of each test script. 592 | # 593 | # Can also be used to stop tests early and skip all remaining tests. For this, 594 | # set skip_all to a string explaining why the tests were skipped before calling 595 | # test_done. 596 | # 597 | # Examples 598 | # 599 | # # Each test script must call test_done at the end. 600 | # test_done 601 | # 602 | # # Skip all remaining tests if prerequisite is not set. 603 | # if ! test_have_prereq PERL; then 604 | # skip_all='skipping perl interface tests, perl not available' 605 | # test_done 606 | # fi 607 | # 608 | # Returns 0 if all tests passed or 1 if there was a failure. 609 | # shellcheck disable=SC2154,SC2034 610 | test_done() { 611 | EXIT_OK=t 612 | 613 | if test -z "$HARNESS_ACTIVE"; then 614 | test_results_dir="$SHARNESS_TEST_OUTDIR/test-results" 615 | mkdir -p "$test_results_dir" 616 | test_results_path="$test_results_dir/$this_test.$$.counts" 617 | 618 | cat >>"$test_results_path" <<-EOF 619 | total $SHARNESS_TEST_NB 620 | success $test_success 621 | fixed $test_fixed 622 | broken $test_broken 623 | failed $test_failure 624 | 625 | EOF 626 | fi 627 | 628 | if test "$test_fixed" != 0; then 629 | say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" 630 | fi 631 | if test "$test_broken" != 0; then 632 | say_color warn "# still have $test_broken known breakage(s)" 633 | fi 634 | if test "$test_broken" != 0 || test "$test_fixed" != 0; then 635 | test_remaining=$((SHARNESS_TEST_NB - test_broken - test_fixed)) 636 | msg="remaining $test_remaining test(s)" 637 | else 638 | test_remaining=$SHARNESS_TEST_NB 639 | msg="$SHARNESS_TEST_NB test(s)" 640 | fi 641 | 642 | case "$test_failure" in 643 | 0) 644 | # Maybe print SKIP message 645 | check_skip_all_ 646 | if test "$test_remaining" -gt 0; then 647 | say_color pass "# passed all $msg" 648 | fi 649 | say "1..$SHARNESS_TEST_NB$skip_all" 650 | 651 | test_eval_ "$final_cleanup" 652 | 653 | remove_trash_ 654 | 655 | exit 0 ;; 656 | 657 | *) 658 | say_color error "# failed $test_failure among $msg" 659 | say "1..$SHARNESS_TEST_NB" 660 | 661 | exit 1 ;; 662 | 663 | esac 664 | } 665 | 666 | : "${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."}" 667 | # Public: Build directory that will be added to PATH. By default, it is set to 668 | # the parent directory of SHARNESS_TEST_DIRECTORY. 669 | export SHARNESS_BUILD_DIRECTORY 670 | PATH="$SHARNESS_BUILD_DIRECTORY:$PATH" 671 | export PATH 672 | 673 | # Public: Path to test script currently executed. 674 | SHARNESS_TEST_FILE="$ARGZERO" 675 | export SHARNESS_TEST_FILE 676 | 677 | # Prepare test area. 678 | SHARNESS_TRASH_DIRECTORY="trash directory.$(basename "$SHARNESS_TEST_FILE" ".$SHARNESS_TEST_EXTENSION")" 679 | test -n "$root" && SHARNESS_TRASH_DIRECTORY="$root/$SHARNESS_TRASH_DIRECTORY" 680 | case "$SHARNESS_TRASH_DIRECTORY" in 681 | /*) ;; # absolute path is good 682 | *) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_OUTDIR/$SHARNESS_TRASH_DIRECTORY" ;; 683 | esac 684 | test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY" 685 | rm -rf "$SHARNESS_TRASH_DIRECTORY" || { 686 | EXIT_OK=t 687 | echo >&5 "FATAL: Cannot prepare test area" 688 | exit 1 689 | } 690 | 691 | 692 | # 693 | # Load any extensions in $testdir/sharness.d/*.sh 694 | # 695 | if test -d "${SHARNESS_TEST_DIRECTORY}/sharness.d" 696 | then 697 | for file in "${SHARNESS_TEST_DIRECTORY}"/sharness.d/*.sh 698 | do 699 | # Ensure glob was not an empty match: 700 | test -e "${file}" || break 701 | 702 | if test -n "$debug" 703 | then 704 | echo >&5 "sharness: loading extensions from ${file}" 705 | fi 706 | # shellcheck disable=SC1090 707 | . "${file}" 708 | if test $? != 0 709 | then 710 | echo >&5 "sharness: Error loading ${file}. Aborting." 711 | exit 1 712 | fi 713 | done 714 | fi 715 | 716 | # Public: Empty trash directory, the test area, provided for each test. The HOME 717 | # variable is set to that directory too. 718 | export SHARNESS_TRASH_DIRECTORY 719 | 720 | HOME="$SHARNESS_TRASH_DIRECTORY" 721 | export HOME 722 | 723 | # shellcheck disable=SC3028 724 | if [ "$OSTYPE" = msys ]; then 725 | USERPROFILE="$SHARNESS_TRASH_DIRECTORY" 726 | export USERPROFILE 727 | fi 728 | 729 | mkdir -p "$SHARNESS_TRASH_DIRECTORY" || exit 1 730 | # Use -P to resolve symlinks in our working directory so that the cwd 731 | # in subprocesses like git equals our $PWD (for pathname comparisons). 732 | cd -P "$SHARNESS_TRASH_DIRECTORY" || exit 1 733 | 734 | check_skip_all_() { 735 | if test -n "$skip_all" && test $SHARNESS_TEST_NB -gt 0; then 736 | error "Can't use skip_all after running some tests" 737 | fi 738 | [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all" 739 | } 740 | 741 | this_test=${SHARNESS_TEST_FILE##*/} 742 | this_test=${this_test%".$SHARNESS_TEST_EXTENSION"} 743 | for skp in $SKIP_TESTS; do 744 | # shellcheck disable=SC2254 745 | case "$this_test" in 746 | $skp) 747 | say_color info >&3 "skipping test $this_test altogether" 748 | skip_all="skip all tests in $this_test" 749 | test_done 750 | esac 751 | done 752 | 753 | test -n "$TEST_LONG" && test_set_prereq EXPENSIVE 754 | test -n "$TEST_INTERACTIVE" && test_set_prereq INTERACTIVE 755 | 756 | # Make sure this script ends with code 0 757 | : 758 | 759 | # vi: set ts=4 sw=4 noet : 760 | --------------------------------------------------------------------------------