3 # Copyright (C) 2019 Free Software Foundation, Inc.
4 # Written by Bruno Haible <bruno@clisp.org>, 2019.
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <https://www.gnu.org/licenses/>.
19 # Program that manages the subdirectories of a git checkout of a package
20 # that come from other packages (called "dependency packages").
22 # This program is similar in spirit to 'git submodule', with three
23 # essential differences:
25 # 1) Its options are easy to remember, and do not require knowledge of
28 # 2) The developer may choose to work on a different checkout for each
29 # dependency package. This is important when the developer is
30 # preparing simultaneous changes to the package and the dependency
31 # package, or is using the dependency package in several packages.
33 # The developer indicates this different checkout by setting the
34 # environment variable <SUBDIR>_SRCDIR (e.g. GNULIB_SRCDIR) to point to it.
36 # 3) The package maintainer may choose to use or not use git submodules.
38 # The advantages of management through a git submodule are:
39 # - Changes to the dependency package cannot suddenly break your package.
40 # In other words, when there is an incompatible change that will cause
41 # a breakage, you can fix things at your pace; you are not forced to
42 # cope with such breakages in an emergency.
43 # - When you need to make a change as a response to a change in the
44 # dependency package, your co-developers cannot accidentally mix things
45 # up (for example, use a combination of your newest change with an
46 # older version of the dependency package).
48 # The advantages of management without a git submodule (just as a plain
49 # subdirectory, let's call it a "subcheckout") are:
50 # - The simplicity: you are conceptually always using the newest revision
51 # of the dependency package.
52 # - You don't have to remember to periodially upgrade the dependency.
53 # Upgrading the dependency is an implicit operation.
55 # This program is meant to be copied to the top-level directory of the package,
56 # together with a configuration file. The configuration is supposed to be
57 # named '.gitmodules' and to define:
58 # * The git submodules, as described in "man 5 gitmodules" or
59 # <https://git-scm.com/docs/gitmodules>. For example:
61 # [submodule "gnulib"]
62 # url = git://git.savannah.gnu.org/gnulib.git
65 # You don't add this piece of configuration to .gitmodules manually. Instead,
67 # $ git submodule add --name "gnulib" -- git://git.savannah.gnu.org/gnulib.git gnulib
69 # * The subdirectories that are not git submodules, in a similar syntax. For
72 # [subcheckout "gnulib"]
73 # url = git://git.savannah.gnu.org/gnulib.git
76 # Here the URL is the one used for anonymous checkouts of the dependency
77 # package. If the developer needs a checkout with write access, they can
78 # either set the GNULIB_SRCDIR environment variable to point to that checkout
79 # or modify the gnulib/.git/config file to enter a different URL.
82 scriptversion
='2019-04-01'
88 # outputs to stdout the --help usage message.
92 Usage: gitsub.sh pull [SUBDIR]
93 gitsub.sh upgrade [SUBDIR]
94 gitsub.sh checkout SUBDIR REVISION
98 gitsub.sh pull [SUBDIR]
99 You should perform this operation after 'git clone ...' and after
101 It brings your checkout in sync with what the other developers of
102 your package have committed and pushed.
103 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
104 value, nothing is done for this SUBDIR.
105 If no SUBDIR is specified, the operation applies to all dependencies.
107 gitsub.sh upgrade [SUBDIR]
108 You should perform this operation periodically, to ensure currency
109 of the dependency package revisions that you use.
110 This operation pulls and checks out the changes that the developers
111 of the dependency package have committed and pushed.
112 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
113 value, nothing is done for this SUBDIR.
114 If no SUBDIR is specified, the operation applies to all dependencies.
116 gitsub.sh checkout SUBDIR REVISION
117 Checks out a specific revision for a dependency package.
118 If an environment variable <SUBDIR>_SRCDIR is set, with a non-empty
119 value, this operation fails.
121 This script requires the git program in the PATH and an internet connection.
126 # outputs to stdout the --version message.
129 year
=`echo "$scriptversion" | sed -e 's/^\(....\)-.*/\1/'`
131 gitsub.sh (GNU gnulib) $scriptversion
132 Copyright (C) 2019-$year Free Software Foundation, Inc.
133 License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
134 This is free software: you are free to change and redistribute it.
135 There is NO WARRANTY, to the extent permitted by law.
137 printf "Written by %s.\n" "Bruno Haible"
140 # func_fatal_error message
141 # outputs to stderr a fatal error message, and terminates the program.
143 # - scriptname name of this program
146 echo "$scriptname: *** $1" 1>&2
147 echo "$scriptname: *** Stop." 1>&2
151 # func_warning message
152 # Outputs to stderr a warning message,
155 echo "gitsub.sh: warning: $1" 1>&2
159 # Outputs to stdout a note message,
162 echo "gitsub.sh: note: $1"
165 # Unset CDPATH. Otherwise, output from 'cd dir' can surprise callers.
166 (unset CDPATH
) >/dev
/null
2>&1 && unset CDPATH
168 # Command-line option processing.
170 while test $# -gt 0; do
172 --help |
--hel |
--he |
--h )
175 --version |
--versio |
--versi |
--vers |
--ver |
--ve |
--v )
179 # Stop option processing
183 echo "gitsub.sh: unknown option $1" 1>&2
184 echo "Try 'gitsub.sh --help' for more information." 1>&2
191 echo "gitsub.sh: missing operation argument" 1>&2
192 echo "Try 'gitsub.sh --help' for more information." 1>&2
196 pull | upgrade | checkout
)
200 echo "gitsub.sh: unknown operation '$1'" 1>&2
201 echo "Try 'gitsub.sh --help' for more information." 1>&2
204 if test $# = 2 && test $mode != checkout ||
test $# -gt 2; then
205 echo "gitsub.sh: too many arguments in '$mode' mode" 1>&2
206 echo "Try 'gitsub.sh --help' for more information." 1>&2
209 if test $# = 0 && test $mode = checkout
; then
210 echo "gitsub.sh: too few arguments in '$mode' mode" 1>&2
211 echo "Try 'gitsub.sh --help' for more information." 1>&2
215 # Read the configuration.
217 # - subcheckout_names space-separated list of subcheckout names
218 # - submodule_names space-separated list of submodule names
219 if test -f .gitmodules
; then
220 subcheckout_names
=`git config --file .gitmodules --get-regexp --name-only 'subcheckout\..*\.url' | sed -e 's/^subcheckout\.//' -e 's/\.url$//' | tr -d '\r' | tr '\n' ' '`
221 submodule_names
=`git config --file .gitmodules --get-regexp --name-only 'submodule\..*\.url' | sed -e 's/^submodule\.//' -e 's/\.url$//' | tr -d '\r' | tr '\n' ' '`
227 # func_validate SUBDIR
228 # Verifies that the state on the file system is in sync with the declarations
229 # in the configuration file.
231 # - subcheckout_names space-separated list of subcheckout names
232 # - submodule_names space-separated list of submodule names
234 # - srcdirvar Environment that the user can set
235 # - srcdir Value of the environment variable
236 # - path if $srcdir = "": relative path of the subdirectory
237 # - needs_init if $srcdir = "" and $path is not yet initialized:
239 # - url if $srcdir = "" and $path is not yet initialized:
243 srcdirvar
=`echo "$1" | LC_ALL=C sed -e 's/[^a-zA-Z0-9]/_/g' | LC_ALL=C tr '[a-z]' '[A-Z]'`"_SRCDIR"
244 eval 'srcdir=$'"$srcdirvar"
247 if test -n "$srcdir"; then
248 func_note
"Ignoring '$1' because $srcdirvar is set."
252 case " $subcheckout_names " in *" $1 "*)
254 # It ought to be a subcheckout.
255 path
=`git config --file .gitmodules "subcheckout.$1.path"`
256 if test -z "$path"; then
259 if test -d "$path"; then
260 if test -d "$path/.git"; then
261 # It's a plain checkout.
264 if test -f "$path/.git"; then
266 func_fatal_error
"Subdirectory '$path' is supposed to be a plain checkout, but it is a submodule."
268 func_warning
"Ignoring '$path' because it exists but is not a git checkout."
272 # The subdir does not yet exist.
274 url
=`git config --file .gitmodules "subcheckout.$1.url"`
275 if test -z "$url"; then
276 func_fatal_error
"Property subcheckout.$1.url is not defined in .gitmodules"
281 case " $submodule_names " in *" $1 "*)
283 # It ought to be a submodule.
284 path
=`git config --file .gitmodules "submodule.$1.path"`
285 if test -z "$path"; then
288 if test -d "$path"; then
289 if test -d "$path/.git" ||
test -f "$path/.git"; then
290 # It's likely a submodule.
293 path_if_empty
=`find "$path" -prune -empty 2>/dev/null`
294 if test -n "$path_if_empty"; then
295 # The subdir is empty.
298 # The subdir is not empty.
299 # It is important to report an error, because we don't want to erase
300 # the user's files and 'git submodule update gnulib' sometimes reports
301 # "fatal: destination path '$path' already exists and is not an empty directory."
302 # but sometimes does not.
303 func_fatal_error
"Subdir '$path' exists but is not a git checkout."
307 # The subdir does not yet exist.
310 # Another way to determine needs_init could be:
311 # if git submodule status "$path" | grep '^-' > /dev/null; then
314 if test -n "$needs_init"; then
315 url
=`git config --file .gitmodules "submodule.$1.url"`
316 if test -z "$url"; then
317 func_fatal_error
"Property submodule.$1.url is not defined in .gitmodules"
323 func_fatal_error
"Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
328 # func_cleanup_current_git_clone
329 # Cleans up the current 'git clone' operation.
332 func_cleanup_current_git_clone
()
335 func_fatal_error
"git clone failed"
339 # Implements the 'pull' operation.
343 if test -z "$srcdir"; then
344 case " $subcheckout_names " in *" $1 "*)
345 # It's a subcheckout.
346 if test -d "$path"; then
347 if test -d "$path/.git"; then
348 (cd "$path" && git pull
) || func_fatal_error
"git operation failed"
351 # The subdir does not yet exist. Create a plain checkout.
352 trap func_cleanup_current_git_clone
1 2 13 15
353 git clone
"$url" "$path" || func_cleanup_current_git_clone
358 case " $submodule_names " in *" $1 "*)
360 if test -n "$needs_init"; then
361 # Create a submodule checkout.
362 git submodule init
-- "$path" && git submodule update
-- "$path" || func_fatal_error
"git operation failed"
364 # See https://stackoverflow.com/questions/1030169/easy-way-to-pull-latest-of-all-git-submodules
365 # https://stackoverflow.com/questions/4611512/is-there-a-way-to-make-git-pull-automatically-update-submodules
366 git submodule update
"$path" || func_fatal_error
"git operation failed"
373 # func_upgrade SUBDIR
374 # Implements the 'upgrade' operation.
378 if test -z "$srcdir"; then
379 if test -d "$path"; then
380 case " $subcheckout_names " in *" $1 "*)
381 # It's a subcheckout.
382 if test -d "$path/.git"; then
383 (cd "$path" && git pull
) || func_fatal_error
"git operation failed"
387 case " $submodule_names " in *" $1 "*)
389 if test -z "$needs_init"; then
390 (cd "$path" && git fetch
&& git merge origin
/master
) || func_fatal_error
"git operation failed"
395 # The subdir does not yet exist.
396 func_fatal_error
"Subdirectory '$path' does not exist yet. Use 'gitsub.sh pull' to create it."
401 # func_checkout SUBDIR REVISION
402 # Implements the 'checkout' operation.
406 if test -z "$srcdir"; then
407 if test -d "$path"; then
408 case " $subcheckout_names " in *" $1 "*)
409 # It's a subcheckout.
410 if test -d "$path/.git"; then
411 (cd "$path" && git checkout
"$2") || func_fatal_error
"git operation failed"
415 case " $submodule_names " in *" $1 "*)
417 if test -z "$needs_init"; then
418 (cd "$path" && git checkout
"$2") || func_fatal_error
"git operation failed"
423 # The subdir does not yet exist.
424 func_fatal_error
"Subdirectory '$path' does not exist yet. Use 'gitsub.sh pull' to create it."
432 for sub
in $subcheckout_names $submodule_names; do
437 for sub
in $subcheckout_names $submodule_names; do
438 if test "$sub" = "$1"; then
445 func_fatal_error
"Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
452 for sub
in $subcheckout_names $submodule_names; do
457 for sub
in $subcheckout_names $submodule_names; do
458 if test "$sub" = "$1"; then
465 func_fatal_error
"Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"
472 for sub
in $subcheckout_names $submodule_names; do
473 if test "$sub" = "$1"; then
478 func_checkout
"$1" "$2"
480 func_fatal_error
"Subdir '$1' is not configured as a subcheckout or a submodule in .gitmodules"