tg.sh: handle help -h
[topgit/pro.git] / t / test-lib-functions-tg.sh
blobe359b2c414f81ee04d17cc01b1332c94c213ca2c
1 # test-lib-functions-tg.sh - test library functions specific to TopGit
2 # Copyright (C) 2017 Kyle J. McKay
3 # All rights reserved
4 # License GPL2
6 # The test library itself is expected to set and export TG_TEST_FULL_PATH as
7 # the full path to the current "tg" executable being tested
9 # IMPORTANT: test-lib-functions-tg.sh MUST NOT EXECUTE ANY CODE!
11 # Also since this file is sourced BEFORE test-lib-functions.sh it may not
12 # override any functions in there either
14 # The function test_lib_functions_tg_init, which should ALWAYS be kept as
15 # the very LAST function in this file, will be called once (AFTER
16 # test-lib-functions.sh has been sourced and inited) but before any other
17 # functions in it are called
21 ## TopGit specific test functions
25 # tg_test_include [-C <dir>] [-r <remote>] [-u] [-f]
27 # Source tg in "tg__include=1" mode to provide access to internal functions
28 # Since this bypasses normal tg options parsing provide a few options
30 # While -C <dir> options are indeed parsed and acted upon and the "include"
31 # itself will occur with the result of any -C <dir> options still active,
32 # the directory will be changed back to the saved "$PWD" (saved at the
33 # beginning of this function) immediately after the "include" takes place.
34 # As a result, if any -C <dir> options are used it may subsequently be
35 # necessary to do an explicit cd <dir> later on before calling any of the
36 # internal tg functions.
38 # This function, obviously, causes the "tg" file to be sourced at the current
39 # shell level so there's no "tg_uninclude" possible, but since the various
40 # test_expect/tolerate functions run the test code itself in a subshell by
41 # default, use of tg_test_include from within a test body will be effectively
42 # local for that specific test body and be "undone" after it's finished.
44 # Note that the special "tg__include" variable IS left set to "1" after this
45 # function returns (but it is NOT exported) and all temporary variables used by
46 # this function are unset
48 # Return status will be "0" on success or last failing status (which will be
49 # from the include itself if that's the last thing to fail)
51 # However, if the "-f" option is passed any failure is immediately fatal
53 # Since the test library always sets TG_TEST_FULL_PATH it's a fatal error to
54 # call this function when that's unset or invalid (not a readable file)
56 # Note that the "-u" option causes the base_remote variable to always be unset
57 # immediately after the include while the "-r <remote>" option causes
58 # the base_remote variable to be set before the include; if neither option is
59 # used and the caller has set base_remote, it will end up as the remote since
60 # the "tg" command will keep it; however, if the base_remote variable is either
61 # unset or empty and the "-u" option is not used when this function is called
62 # then any base_remote setting that "tg" itself picks up will be kept
64 tg_test_include() {
65 [ -f "$TG_TEST_FULL_PATH" ] && [ -r "$TG_TEST_FULL_PATH" ] ||
66 fatal "tg_test_include called while TG_TEST_FULL_PATH is unset or invalid"
67 unset_ _tgf_noremote _tgf_fatal _tgf_curdir _tgf_errcode
68 _tgf_curdir="$PWD"
69 while [ $# -gt 0 ]; do case "$1" in
70 -h|--help)
71 unset_ _tgf_noremote _tgf_fatal _tgf_curdir
72 echo "tg_test_include [-C <dir>] [-r <remote>] [-u] [-f]"
73 return 0
75 -C)
76 shift
77 [ -n "$1" ] || fatal "tg_test_include option -C requires an argument"
78 cd "$1" || return 1
80 -u)
81 _tgf_noremote=1
83 -r)
84 shift
85 [ -n "$1" ] || fatal "tg_test_include option -r requires an argument"
86 base_remote="$1"
87 unset_ _tgf_noremote
89 -f)
90 _tgf_fatal=1
92 --)
93 shift
94 break
96 -?*)
97 echo "tg_test_include: unknown option: $1" >&2
98 usage 1
101 break
103 esac; shift; done
104 [ $# -eq 0 ] || fatal "tg_test_include non-option arguments prohibited: $*"
105 unset_ tg__include # make sure it's not exported
106 tg__include=1
107 # MUST do this AFTER changing the current directory since it sets $git_dir!
108 _tgf_errcode=0
109 . "$TG_TEST_FULL_PATH" || _tgf_errcode=$?
110 [ -z "$_tgf_noremote" ] || base_remote=
111 cd "$_tgf_curdir" || _fatal "tg_test_include post-include cd failed to: $_tgf_curdir"
112 set -- "$_tgf_errcode" "$_tgf_fatal"
113 unset_ _tgf_noremote _tgf_fatal _tgf_curdir _tgf_errcode
114 [ -z "$2" ] || [ "$1" = "0" ] || _fatal "tg_test_include sourcing of tg failed with status $1"
115 return $1
119 # tg_test_setup_topgit [-C <dir>] [-f]
121 tg_test_setup_topgit() {
122 [ -f "$TG_TEST_FULL_PATH" ] && [ -r "$TG_TEST_FULL_PATH" ] ||
123 fatal "tg_test_setup_topgit called while TG_TEST_FULL_PATH is unset or invalid"
124 unset_ _tgf_fatal _tgf_curdir _tgf_td
125 _tgf_curdir="$PWD"
126 while [ $# -gt 0 ]; do case "$1" in
127 -h|--help)
128 unset_ _tgf_fatal _tgf_curdir
129 echo "tg_test_setup_topgit [-C <dir>] [-f]"
130 return 0
133 shift
134 [ -n "$1" ] || fatal "tg_test_include option -C requires an argument"
135 cd "$1" || return 1
138 _tgf_fatal=1
141 shift
142 break
144 -?*)
145 echo "tg_test_setup_topgit: unknown option: $1" >&2
146 usage 1
149 break
151 esac; shift; done
152 [ $# -eq 0 ] || fatal "tg_test_setup_topgit non-option arguments prohibited: $*"
153 _tgf_td="$(test_get_temp -d setup)" || failt "tg_test_setup_topgit test_get_temp failed"
154 _tgf_td_script="$_tgf_td/${TG_TEST_FULL_PATH##*/}"
155 write_script "$_tgf_td_script" <<-'KLUDGE'
156 # without this $0 will be wrong and the installed hook will never run
157 tg__include=1 &&
158 PATH="${TG_TEST_FULL_PATH%/*}:$PATH" && export PATH &&
159 . "$TG_TEST_FULL_PATH" &&
160 setup_ours &&
161 setup_hook "pre-commit"
162 KLUDGE
163 _tgf_errcode=0
164 TG_TEST_FULL_PATH="$TG_TEST_FULL_PATH" "$_tgf_td_script" || _tgf_errcode=$?
165 cd "$_tgf_curdir" || _fatal "tg_test_setup_topgit post-setup cd failed to: $_tgf_curdir"
166 set -- "$_tgf_errcode" "$_tgf_fatal"
167 rm -rf "$_tgf_td"
168 unset_ _tgf_fatal _tgf_curdir _tgf_errcode _tgf_td _tgf_td_script
169 [ -z "$2" ] || [ "$1" = "0" ] || _fatal "tg_test_setup_topgit failed with status $1"
170 return $1
174 # tg_test_v_getbases <varname> [<remotename>]
176 # If tg_test_bases is unset or "" the default for the tg being tested is used.
177 # Otherwise it must be set to "refs" or "heads" or a fatal error will occur.
179 # The variable named by <varname> is set to the full ref prefix for top-bases
180 # (as selected by tg_test_bases). If <remotename> is non-empty it will be the
181 # prefix for top-bases under that remote's ref namespace.
183 # Example return values:
185 # tg_test_v_getbases x # x='refs/top-bases' # "refs"
186 # tg_test_v_getbases x # x='refs/heads/{top-bases}' # "heads"
187 # tg_test_v_getbases x origin # x='refs/remotes/origin/top-bases' # "refs"
188 # tg_test_v_getbases x origin # x='refs/remotes/origin/{top-bases}' # "heads"
190 tg_test_v_getbases() {
191 [ -n "$1" ] || fatal "tg_test_v_getbases called without varname argument"
192 [ $# -le 2 ] || fatal "tg_test_v_getbases called with more than two arguments"
193 case "${tg_test_bases:-$tg__top_bases}" in
194 "refs") _tgbases="top-bases";;
195 "heads") _tgbases="heads/{top-bases}";;
196 *) fatal "tg_test_v_getbases called with invalid tg_test_bases setting \"$tg_test_bases\"";;
197 esac
198 if [ -n "$2" ]; then
199 set -- "$1" "refs/remotes/$2/${_tgbases#heads/}"
200 else
201 set -- "$1" "refs/$_tgbases"
203 unset_ _tgbases
204 eval "$1"'="$2"'
208 # tg_test_v_getremote <varname> [<remotename>]
210 # Set the variable named by <varname> to <remotename> unless <remotename> is
211 # omitted or empty in which case use the value of tg_test_remote unless it's
212 # empty in which case fail with a fatal error.
214 tg_test_v_getremote() {
215 [ -n "$1" ] || fatal "tg_test_getremote called without varname argument"
216 [ $# -le 2 ] || fatal "tg_test_getremote called with more than two arguments"
217 [ -n "${2:-$tg_test_remote}" ] ||
218 fatal "tg_test_getremote called with no remote name argument and \$tg_test_remote not set"
219 eval "$1"'="${2:-$tg_test_remote}"'
223 # tg_test_bare_tree [-C <dir>] <treeish>
225 # Output the hash of the tree that's equivalent to <treeish>'s tree with any
226 # .topdeps and .topmsg files removed, If <treeish> does not have any top-level
227 # .topdeps or .topmsg files then the result will be the same as <treeish>^{tree}
228 tg_test_bare_tree() {
229 _tct_dir="."
230 [ $# -lt 2 ] || [ "$1" != "-C" ] || { shift; _tct_dir="${1:-.}"; shift; }
231 [ -d "$_tct_dir" ] ||
232 fatal "tg_test_bare_tree no such directory \"$_tct_dir\""
233 [ $# -eq 1 ] && [ -n "$1" ] ||
234 fatal "tg_test_bare_tree missing treeish argument"
235 set -- "$_tct_dir" "$1"
236 unset_ _tct_dir
237 git -C "$1" ls-tree --full-tree "$2^{tree}" |
238 # The first character after the '/' in the sed pattern is a literal tab
239 sed -e '/ \.topdeps$/d' -e '/ \.topmsg$/d' |
240 git -C "$1" mktree
244 # tg_test_create_branch [-C <dir>] [--notick] [+][\][[<remote>]:[:]]<branch> [--no-topmsg] [-m "message"] [:[:[:]][~]]<start> [<dep>...]
246 # Create a new TopGit branch named <branch> in the current repository (or
247 # <dir> if given).
249 # Unless --notick is used the test_tick function will be called just prior to
250 # the creation of each new commit.
252 # All of the new ref names to be created must be non-existent unless the "+"
253 # prefix is used and then they're just overwritten if they exist. A leading
254 # "\" will be stripped so branch names starting with "+" can be used without
255 # needing to use the overwrite option.
257 # tg_test_v_getbases is used so tg_test_bases must be unset or "refs" or "heads".
259 # If [-m "message"] is omitted the message will be "branch <branch>" and
260 # the same with "[PATCH] " prepended for .topmsg (unless it's a bare branch).
261 # A "[PATCH] " value will be prepended to any -m value when creating the
262 # .topmsg file unless "message" starts with a "[" character. However, with
263 # --no-topmsg the .topmsg file is omitted entirely just like a bare branch, but
264 # it's still possible to have a .topdeps file in this case (the message will
265 # still be used for the commit message if given).
267 # The branch will start from <start> which must be the name of a ref located
268 # under refs/heads i.e. refs/heads/<start>. <start> will become the first
269 # dependency in the .topdeps file. Any additional <dep> names must also be
270 # Git branch names and will be listed in the order given as additional lines
271 # in the .topdeps file.
273 # If <start> begins with one ":" it may be any committish AND it will be
274 # omitted from the .topdeps file so the .topdeps file will then be empty
275 # unless at least one [<dep>...] is given.
277 # If <start> begins with ":~" it may be any committish AND the .topdeps file
278 # will be omitted entirely just like a bare branch, but it's still possible to
279 # have a .topmsg file in this case.
281 # If <start> begins with two colons "::" it may be any committish, but
282 # [<dep>...] MUST BE OMITTED and the created TopGit branch will be bare
283 # (i.e. it will have neither a .topmsg nor a .topdeps file at all). This is
284 # really just a convenience shortcut to save typing because the same thing can
285 # be accomplished with --no-topmsg and :~<start> instead.
287 # If <start> begins with three colons ":::" it may be any committish, but
288 # [<dep>...] MUST BE OMITTED and the created branch will not have any
289 # corresponding base(s) and will be bare (i.e. it will be a non-TopGit branch)
290 # and will have a single file added in the style of the test_commit function
291 # where the tag will be omitted if the message contains whitespace or any other
292 # invalid ref name characters.
294 # Additionally if <start> begins with a colon (or two or three) it may be the
295 # empty string to start from a new empty tree root commit.
297 # If <remote>:<branch> is used a remote TopGit branch will be created instead
298 # in which case the :<start> form may be most useful as it can be used to
299 # specify a starting point not under refs/heads.
301 # If <remote>::<branch> is used then both a local and remote branch will be
302 # created (both with the same branch and base values).
304 # If <remote> is empty (but the trailing colon is still present) the value of
305 # the tg_test_remote variable will be used for the remote name (in which case
306 # if tg_test_remote is empty a fatal error will occur).
308 # The current working tree, index and symolic-ref setting of HEAD are left
309 # completely untouched by this function although if HEAD is a symbolic ref to
310 # <branch> and <branch> is unborn, it WILL be created by this function which
311 # will impact Git's view of the working tree and index in that case.
313 # Note that NO CHECKING is done on the <dep> values whatsoever! They're just
314 # dumped into the .topdeps file as-is if given.
316 tg_test_create_branch() {
317 _tcb_dir="."
318 [ $# -lt 2 ] || [ "$1" != "-C" ] || { shift; _tcb_dir="${1:-.}"; shift; }
319 [ -d "$_tcb_dir" ] ||
320 fatal "tg_test_create_branch: no such directory \"$_tcb_dir\""
321 _tcb_nto=
322 [ "$1" != "--notick" ] || { _tcb_nto="$1"; shift; }
323 [ $# -ge 1 ] && [ -n "$1" ] ||
324 fatal "tg_test_create_branch: missing <branch> name to create"
325 _tcb_new="$1"
326 shift
327 _tcb_bases=
328 _tcb_rmt=
329 _tcb_rmtonly=
330 _tcb_rmtbases=
331 _tcb_nooverwrite=1
332 case "$_tcb_new" in "+"*)
333 _tcb_new="${_tcb_new#?}"
334 _tcb_nooverwrite=
335 esac
336 case "$_tcb_new" in "\\"*) _tcb_new="${_tcb_new#?}"; esac
337 case "$_tcb_new" in
338 *::*)
339 _tcb_rmt="${_tcb_new%%::*}"
340 tg_test_v_getremote _tcb_rmt "$_tcb_rmt"
341 _tcb_new="${_tcb_new#*::}"
343 *:*)
344 _tcb_rmt="${_tcb_new%%:*}"
345 tg_test_v_getremote _tcb_rmt "$_tcb_rmt"
346 _tcb_new="${_tcb_new#*:}"
347 _tcb_rmtonly=1
349 esac
350 [ -n "$_tcb_new" ] ||
351 fatal "tg_test_create_branch: invalid empty <branch> name"
352 [ -n "$_tcb_rmtonly" ] || tg_test_v_getbases _tcb_bases
353 [ -z "$_tcb_rmt" ] || tg_test_v_getbases _tcb_rmtbases "$_tcb_rmt"
354 _tcb_notm=
355 [ "$1" != "--no-topmsg" ] || { shift; _tcb_notm=1; }
356 _tcb_msg=
357 [ $# -lt 2 ] || [ "$1" != "-m" ] || { shift; _tcb_msg="$1"; shift; }
358 [ -n "$_tcb_msg" ] || _tcb_msg="branch $_tcb_new"
359 [ $# -ge 1 ] && [ -n "$1" ] ||
360 fatal "tg_test_create_branch: missing <start> point argument"
361 _tcb_start="$1"
362 shift
363 _tcb_notd=
364 _tcb_plain=
365 _tcb_sdep="$_tcb_start"
366 _tcb_vref="refs/heads/$_tcb_start"
367 case "$_tcb_start" in
368 ":::"*)
369 _tcb_sdep=
370 _tcb_vref="${_tcb_start#:::}"
371 _tcb_vref="${_tcb_vref#~}"
372 _tcb_notd=1
373 _tcb_notm=1
374 _tcb_plain=1
376 "::"*)
377 _tcb_sdep=
378 _tcb_vref="${_tcb_start#::}"
379 _tcb_vref="${_tcb_vref#~}"
380 _tcb_notd=1
381 _tcb_notm=1
383 ":~"*)
384 _tcb_sdep=
385 _tcb_vref="${_tcb_start#:~}"
386 _tcb_notd=1
388 ":"*)
389 _tcb_sdep=
390 _tcb_vref="${_tcb_start#:}"
392 esac
393 if [ -z "$_tcb_sdep$_tcb_vref$_tcb_plain" ]; then
394 _tcb_btr="$(git -C "$_tcb_dir" mktree </dev/null)" ||
395 fatal "tg_test_create_branch: git mktree failed making empty tree"
396 [ -n "$_tcb_nto" ] || test_tick
397 _tcb_vref="$(git -C "$_tcb_dir" commit-tree </dev/null \
398 -m "tg_test_create_branch $_tcb_new root" "$_tcb_btr")" && [ -n "$_tcb_vref" ] ||
399 fatal "tg_test_create_branch: git commit-tree failed committing new root"
401 _tcb_scmt=
402 if [ -n "$_tcb_vref" ]; then
403 _tcb_scmt="$(git -C "$_tcb_dir" rev-parse --quiet --verify "$_tcb_vref^0" --)" && [ -n "$_tcb_scmt" ] ||
404 fatal "tg_test_create_branch: invalid starting point \"$_tcb_vref\""
406 [ $# -eq 0 ] || [ -z "$_tcb_notd" ] ||
407 fatal "tg_test_create_branch: no <dep> arguments allowed for $(b=${_tcb_notm:+bare}&&echo ${b:-non-topdeps}) branch"
408 if [ -n "$_tcb_nooverwrite" ]; then
410 [ -n "$_tcb_rmtonly" ] ||
411 printf '%s\n' "verify refs/heads/$_tcb_new" "verify $_tcb_bases/$_tcb_new"
412 [ -z "$_tcb_rmt" ] ||
413 printf '%s\n' "verify refs/remotes/$_tcb_rmt/$_tcb_new" "verify $_tcb_rmtbases/$_tcb_new"
414 } | git -C "$_tcb_dir" update-ref --stdin ||
415 fatal "tg_test_create_branch: branch \"$_tcb_new\" already exists"
417 if [ -z "$_tcb_plain" ]; then
418 _tcb_btr="$(tg_test_bare_tree -C "$_tcb_dir" "$_tcb_scmt")" && [ -n "$_tcb_btr" ] ||
419 fatal "tg_test_create_branch: tg_test_bare_tree failed on \"$_tcb_scmt\" ($_tcb_vref^0)"
420 if [ "$_tcb_btr" != "$(git -C "$_tcb_dir" rev-parse --quiet --verify "$_tcb_scmt^{tree}" --)" ]; then
421 [ -n "$_tcb_nto" ] || test_tick
422 _tcb_scmt="$(git -C "$_tcb_dir" commit-tree </dev/null -p "$_tcb_scmt" \
423 -m "tg_test_create_branch $_tcb_new base" "$_tcb_btr")" && [ -n "$_tcb_scmt" ] ||
424 fatal "tg_test_create_branch: git commit-tree failed"
427 _tcb_hcmt="$_tcb_scmt"
428 if [ z"$_tcb_notd$_tcb_notm" != z"11" ]; then
429 [ -z "$_tcb_sdep" ] || set -- "$_tcb_sdep" "$@"
430 _tcb_fmt=
431 [ $# -eq 0 ] || _tcb_fmt='%s\n'
432 _tcb_dps=
433 [ -n "$_tcb_notd" ] || {
434 _tcb_dps="$(printf "$_tcb_fmt" "$@" | git -C "$_tcb_dir" hash-object -t blob -w --stdin)" && [ -n "$_tcb_dps" ] ||
435 fatal "tg_test_create_branch: git hash-object failed creating .topdeps blob"
437 _tcb_tms=
438 if [ -z "$_tcb_notm" ]; then
439 case "$_tcb_msg" in
440 "["*) _tcb_sbj="Subject: $_tcb_msg";;
441 *) _tcb_sbj="Subject: [PATCH] $_tcb_msg";;
442 esac
443 _tcb_tms="$(printf '%s\n' "From: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" "$_tcb_sbj" |
444 git -C "$_tcb_dir" hash-object -t blob -w --stdin)" && [ -n "$_tcb_tms" ] ||
445 fatal "tg_test_create_branch: git hash-object failed creating .topmsg blob"
447 _tcb_htr="$({
448 git -C "$_tcb_dir" ls-tree --full-tree "$_tcb_hcmt^{tree}" &&
449 printf '100644 blob %s\011%s\n' $_tcb_dps ${_tcb_dps:+.topdeps} $_tcb_tms ${_tcb_tms:+.topmsg}
450 } | git -C "$_tcb_dir" mktree)" && [ -n "$_tcb_htr" ] ||
451 fatal "tg_test_create_branch: git mktree failed creating new branch \"$_tcb_new\" tree"
452 [ -n "$_tcb_nto" ] || test_tick
453 _tcb_hcmt="$(git -C "$_tcb_dir" commit-tree </dev/null -p "$_tcb_hcmt" -m "$_tcb_msg" "$_tcb_htr")" && [ -n "$_tcb_hcmt" ] ||
454 fatal "tg_test_create_branch: git commit-tree failed"
455 elif [ -n "$_tcb_plain" ]; then
456 read -r _tcb_fnm <<-EOT
457 $_tcb_msg
459 _tcb_blb="$(printf '%s\n' "$_tcb_msg" | git -C "$_tcb_dir" hash-object -t blob -w --stdin)" && [ -n "$_tcb_blb" ] ||
460 fatal "tg_test_create_branch: git hash-object failed creating file \"$_tcb_fnm.t\" blob"
461 _tcb_htr="$({
462 [ -z "$_tcb_hcmt" ] || git -C "$_tcb_dir" ls-tree --full-tree "$_tcb_hcmt^{tree}"
463 printf '100644 blob %s\011%s\n' "$_tcb_blb" "$_tcb_fnm.t"
464 } | git -C "$_tcb_dir" mktree)" && [ -n "$_tcb_htr" ] ||
465 fatal "tg_test_create_branch: git mktree failed creating new plain branch \"$_tcb_new\" tree"
466 [ -n "$_tcb_nto" ] || test_tick
467 _tcb_hcmt="$(git -C "$_tcb_dir" commit-tree </dev/null ${_tcb_hcmt:+-p} $_tcb_hcmt -m "$_tcb_msg" "$_tcb_htr")" && [ -n "$_tcb_hcmt" ] ||
468 fatal "tg_test_create_branch: git commit-tree failed"
469 ! test_check_one_tag_ $_tcb_msg || git -C "$_tcb_dir" tag $_tcb_msg "$_tcb_hcmt"
471 set -- "$_tcb_dir" "$_tcb_new" "$_tcb_overwrite"
472 if [ -n "$_tcb_rmt" ]; then
473 [ -n "$_tcb_plain" ] || set -- "$@" "$_tcb_rmtbases/$_tcb_new" "$_tcb_scmt"
474 set -- "$@" "refs/remotes/$_tcb_rmt/$_tcb_new" "$_tcb_hcmt"
476 if [ -z "$_tcb_rmtonly" ]; then
477 [ -n "$_tcb_plain" ] || set -- "$@" "$_tcb_bases/$_tcb_new" "$_tcb_scmt"
478 set -- "$@" "refs/heads/$_tcb_new" "$_tcb_hcmt"
480 unset_ _tcb_dir _tcb_nto _tcb_new _tcb_bases _tcb_rmt _tcb_rmtonly _tcb_rmtbases _tcb_msg \
481 _tcb_start _tcb_notd _tcb_sdep _tcb_vref _tcb_scmt _tcb_btr _tcb_hcmt _tcb_fmt _tcb_dps \
482 _tcb_tms _tcb_htr _tcb_notm _tcb_nooverwrite || :
483 (_ovwno="${3:+ }" && shift 3 && printf "update %s %s$_ovwno"'\n' "$@") | git -C "$1" update-ref --stdin ||
484 fatal "tg_test_create_branch: update-ref for branch \"$2\" failed"
488 # tg_test_create_branches [-C <dirpath>] [--notick]
490 # Read `tg_branch_create` instructions from standard input and then call
491 # tg_create_branch for each set of instructions.
493 # Standard input must be a sequence of line groups each consisting of two or
494 # more non-empty lines where the groups are separated by a single blank line.
495 # Each line group must have this form:
497 # [+][\][[<remote>]:[:]]<branch> [--no-topmsg] [optional] [message] [here]
498 # [[delete]:[:[:]][~]]<start>
499 # [<dep>]
500 # [<dep>]
501 # ...
503 # Note that any <dep> lines must not be empty. If there are no <dep>s, then
504 # there must be no <dep> lines at all.
506 # See the description of tg_test_create_branch for the meaning of the
507 # arguments. The provided <dirpath> and --notick options are passed along
508 # on each call to the tg_test_create_branch function.
510 # Interpretation of "delete:" lines is handled here. No message or deps
511 # are allowed. If <start> is non-empty, all refs (1, 2 or 4) to be deleted
512 # must have that value. If the "+" is NOT present and <start> is empty, all
513 # refs to be deleted MUST actually exist.
515 # Since each line group represents a call to tg_test_create_branch, later
516 # groups may use any branch name created by an earlier group as a <start>
517 # point without problem.
519 tg_test_create_branches() {
520 _tcbs_dir="."
521 [ $# -lt 2 ] || [ "$1" != "-C" ] || { shift; _tcbs_dir="${1:-.}"; shift; }
522 [ -d "$_tcbs_dir" ] ||
523 fatal "tg_test_create_branches: no such directory \"$_tcbs_dir\""
524 _tcbs_nto=
525 [ "$1" != "--notick" ] || { _tcbs_nto="$1"; shift; }
526 [ $# -eq 0 ] ||
527 fatal "tg_test_create_branches: invalid extra arguments: $*"
528 _tcbs_bname=
529 _tcbs_bmsg=
530 _tcbs_bstrt=
531 _tcbs_lno=1
532 _tcbs_dep=
533 _tcbs_glno="$_tcbs_lno"
534 _tcbs_tick=
535 [ -n "$_tcbs_nto" ] || _tcbs_tick="$(test_get_temp test_tick)"
536 while read -r _tcbs_bname _tcbs_bmsg && [ -n "$_tcbs_bname" ]; do
537 _tcbs_lno="$(( $_tcbs_lno + 1 ))"
538 read -r _tcbs_strt && [ -n "$_tcbs_strt" ] ||
539 fatal "tg_test_create_branches: missing <start> at stdin line $_tcbs_lno"
540 _tcbs_lno="$(( $_tcbs_lno + 1 ))"
541 _tcbs_dntm=
542 case "$_tcbs_bmsg" in "--no-topmsg"|"--no-topmsg "*)
543 _tcbs_dntm="--no-topmsg"
544 _tcbs_bmsg="${_tcbs_bmsg#--no-topmsg}"
545 _tcbs_bmsg="${_tcbs_bmsg# }"
546 esac
547 set -- "-C" "$_tcbs_dir" $_tcbs_nto "$_tcbs_bname" $_tcbs_dntm "-m" "$_tcbs_bmsg" "$_tcbs_strt"
548 _tcbs_nod=1
549 while read -r _tcbs_dep && [ -n "$_tcbs_dep" ]; do
550 _tcbs_lno="$(( $_tcbs_lno + 1 ))"
551 set -- "$@" "$_tcbs_dep"
552 _tcbs_nod=
553 done
554 _tcbs_lno="$(( $_tcbs_lno + 1 ))"
555 case "$_tcbs_strt" in
556 "delete:"*)
557 [ -z "$_tcbs_bmsg" ] ||
558 fatal "tg_test_create_branches: \"delete:...\" usage prohibits msg at stdin line $_tcbs_glno"
559 [ -n "$_tcbs_nod" ] ||
560 fatal "tg_test_create_branches: \"delete:...\" usage prohibits deps at stdin line $_tcbs_glno"
561 _tcbs_rbs=
562 case "$_tcbs_strt" in
563 "delete:::"*)
564 _tcbs_strt="${_tcbs_strt#delete:::}"
566 "delete::"*)
567 _tcbs_strt="${_tcbs_strt#delete::}"
568 _tcbs_rbs=1
570 "delete:"*)
571 _tcbs_strt="${_tcbs_strt#delete:}"
572 _tcbs_rbs=1
574 esac
575 _tcbs_bases=
576 _tcbs_rmt=
577 _tcbs_rmtonly=
578 _tcbs_rmtbases=
579 _tcbs_mustexist=1
580 case "$_tcbs_bname" in "+"*)
581 _tcbs_bname="${_tcbs_bname#?}"
582 _tcbs_mustexist=
583 esac
584 case "$_tcbs_bname" in "\\"*) _tcbs_bname="${_tcbs_bname#?}"; esac
585 case "$_tcbs_bname" in
586 *::*)
587 _tcbs_rmt="${_tcbs_bname%%::*}"
588 tg_test_v_getremote _tcbs_rmt "$_tcbs_rmt"
589 _tcbs_bname="${_tcbs_bname#*::}"
591 *:*)
592 _tcbs_rmt="${_tcbs_bname%%:*}"
593 tg_test_v_getremote _tcbs_rmt "$_tcbs_rmt"
594 _tcbs_bname="${_tcbs_bname#*:}"
595 _tcbs_rmtonly=1
597 esac
598 [ -n "$_tcbs_bname" ] ||
599 fatal "tg_test_create_branches: invalid empty delete: <branch> name"
600 if [ -n "$_tcbs_rbs" ]; then
601 [ -n "$_tcbs_rmtonly" ] || tg_test_v_getbases _tcbs_bases
602 [ -z "$_tcbs_rmt" ] || tg_test_v_getbases _tcbs_rmtbases "$_tcbs_rmt"
604 set --
605 _tcbs_fmt='delete %s %s\n'
606 [ -n "$_tcbs_strt" ] || [ -n "$_tcbs_mustexist" ] || _tcbs_fmt='delete %s\n'
607 if [ -n "$_tcbs_rmt" ]; then
608 if [ -n "$_tcbs_rbs" ]; then
609 set -- "$@" "$_tcbs_rmtbases/$_tcb_bname"
610 [ -z "$_tcbs_strt" ] && [ -z "$_tcbs_mustexist" ] ||
611 set -- "$@" "${_tcbs_strt:-$_tcbs_rmtbases/$_tcb_bname}"
613 set -- "$@" "refs/remotes/$_tcbs_rmt/$_tcbs_bname"
614 [ -z "$_tcbs_strt" ] && [ -z "$_tcbs_mustexist" ] ||
615 set -- "$@" "${_tcbs_strt:-refs/remotes/$_tcbs_rmt/$_tcbs_bname}"
617 if [ -z "$_tcbs_rmtonly" ]; then
618 if [ -n "$_tcbs_rbs" ]; then
619 set -- "$@" "$_tcbs_bases/$_tcbs_bname"
620 [ -z "$_tcbs_strt" ] && [ -z "$_tcbs_mustexist" ] ||
621 set -- "$@" "${_tcbs_strt:-$_tcbs_bases/$_tcbs_bname}"
623 set -- "$@" "refs/heads/$_tcbs_bname"
624 [ -z "$_tcbs_strt" ] && [ -z "$_tcbs_mustexist" ] ||
625 set -- "$@" "${_tcbs_strt:-refs/heads/$_tcbs_bname}"
627 printf "$_tcbs_fmt" "$@" | git -C "$_tcbs_dir" update-ref --stdin ||
628 fatal "tg_test_create_branches: failed deleting branch \"$_tcbs_bname\" group at stdin line $_tcbs_glno"
631 (tg_test_create_branch "$@" && { [ -z "$_tcbs_tick" ] || echo "$test_tick" >"$_tcbs_tick"; } ) ||
632 fatal "tg_test_create_branches: failed creating branch \"$_tcbs_bname\" group at stdin line $_tcbs_glno"
633 [ -z "$_tcbs_tick" ] || { read -r test_tick <"$_tcbs_tick" || :; rm "$_tcbs_tick"; }
635 esac
636 _tcbs_glno="$_tcbs_lno"
637 done
638 [ -z "$_tcbs_tick" ] || ! [ -e "$_tcbs_tick" ] || rm "$_tcbs_tick"
639 unset_ _tcbs_dir _tcbs_nto _tcbs_name _tcbs_bmsg _tcbs_bstrt _tcbs_lno \
640 _tcbs_dep _tcbs_glno _tcbs_tick _tcbs_nod _tcbs_rbs _tcbs_fmt _tcbs_dntm \
641 _tcbs_bases _tcbs_rmt _tcbs_rmtonly _tcbs_rmtbases _tcbs_mustexist || :
642 return 0
646 # tg_test_create_tag [-C <dirpath>] [--notick] [-t] <newtag> [<for-each-ref-pat>...]
648 # If no for-each-ref patterns are given then refs/heads, refs/top-bases and
649 # refs/remotes are used. Only refs that have type `commit` will be put in
650 # the tag. The result will be a new annotated tag <newtag> that can be used
651 # as a source for `tg revert` (and therefore the `tg -w` option too).
653 # The for-each-ref-pat arguments are passed directly to `git for-each-ref`
654 # and may therefore use all wildcard features available with that command.
656 # Unlike ordinary `tg tag` tags, the resulting tags tag the empty blob (or with
657 # -t the empty tree) and there is no consolidation commit made at all, ever.
659 tg_test_create_tag() {
660 _tct_dir="."
661 [ $# -lt 2 ] || [ "$1" != "-C" ] || { shift; _tct_dir="${1:-.}"; shift; }
662 [ -d "$_tct_dir" ] ||
663 fatal "tg_test_create_tag: no such directory \"$_tct_dir\""
664 _tct_nto=
665 [ "$1" != "--notick" ] || { _tct_nto="$1"; shift; }
666 _tct_obj="blob"
667 [ "$1" != "-t" ] || { _tct_obj="tree"; shift; }
668 [ $# -ge 1 ] ||
669 fatal "tg_test_create_tag: missing tag name argument"
670 _tct_tname="$1" && shift
671 _tct_refs="$(test_get_temp tagrefs)"
672 [ $# -gt 0 ] || set -- "refs/heads" "refs/remotes" "refs/top-bases"
673 git -C "$_tct_dir" for-each-ref --format="%(objecttype) %(objectname) %(refname)" "$@" |
674 sed -n 's/^commit //p' >"$_tct_refs"
675 _tct_mt="$(git -C "$_tct_dir" hash-object --stdin -t "$_tct_obj" -w </dev/null)"
676 [ -n "$_tct_nto" ] || test_tick
677 _tct_id="$(git var GIT_COMMITTER_IDENT)"
678 [ $(wc -l <"$_tct_refs") -gt 0 ] || return 1
679 _tct_tag="$({
680 printf '%s\n' \
681 "object $_tct_mt" \
682 "type $_tct_obj" \
683 "tag $_tct_tname" \
684 "tagger $_tct_id" \
685 "" \
686 "-----BEGIN TOPGIT REFS-----"
687 cat "$_tct_refs" && printf '%s\n' \
688 "-----END TOPGIT REFS-----"
689 } | git -C "$_tct_dir" hash-object --stdin -t tag -w)" || return 1
690 rm -f "$_tct_refs"
691 git update-ref "refs/tags/$_tct_tname" "$_tct_tag" "" || return 1
692 unset_ _tct_dir _tct_nto _tct_obj _tct_tname _tct_refs _tct_mt _tct_id _tct_tag
693 return 0
698 ## TopGit specific test functions "init" function
703 # THIS SHOULD ALWAYS BE THE LAST FUNCTION DEFINED IN THIS FILE
705 # Any client that sources this file should immediately call this function
706 # afterwards
708 # THERE SHOULD NOT BE ANY DIRECTLY EXECUTED LINES OF CODE IN THIS FILE
710 test_lib_functions_tg_init() {
711 # Nothing to do here yet, but a function must have at least one command