Code

59fe7b335c7f87f41810be3c3e2a3c5aa0d1d9b0
[git.git] / git-submodule.sh
1 #!/bin/sh
2 #
3 # git-submodules.sh: add, init, update or list git submodules
4 #
5 # Copyright (c) 2007 Lars Hjemli
7 USAGE="[--quiet] [--cached] \
8 [add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
9 [--] [<path>...]|[foreach <command>]"
10 OPTIONS_SPEC=
11 . git-sh-setup
12 . git-parse-remote
13 require_work_tree
15 command=
16 branch=
17 quiet=
18 cached=
20 #
21 # print stuff on stdout unless -q was specified
22 #
23 say()
24 {
25         if test -z "$quiet"
26         then
27                 echo "$@"
28         fi
29 }
31 # Resolve relative url by appending to parent's url
32 resolve_relative_url ()
33 {
34         remote=$(get_default_remote)
35         remoteurl=$(git config "remote.$remote.url") ||
36                 die "remote ($remote) does not have a url defined in .git/config"
37         url="$1"
38         while test -n "$url"
39         do
40                 case "$url" in
41                 ../*)
42                         url="${url#../}"
43                         remoteurl="${remoteurl%/*}"
44                         ;;
45                 ./*)
46                         url="${url#./}"
47                         ;;
48                 *)
49                         break;;
50                 esac
51         done
52         echo "$remoteurl/$url"
53 }
55 #
56 # Get submodule info for registered submodules
57 # $@ = path to limit submodule list
58 #
59 module_list()
60 {
61         git ls-files --stage -- "$@" | grep '^160000 '
62 }
64 #
65 # Map submodule path to submodule name
66 #
67 # $1 = path
68 #
69 module_name()
70 {
71         # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
72         re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
73         name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
74                 sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
75        test -z "$name" &&
76        die "No submodule mapping found in .gitmodules for path '$path'"
77        echo "$name"
78 }
80 #
81 # Clone a submodule
82 #
83 # Prior to calling, cmd_update checks that a possibly existing
84 # path is not a git repository.
85 # Likewise, cmd_add checks that path does not exist at all,
86 # since it is the location of a new submodule.
87 #
88 module_clone()
89 {
90         path=$1
91         url=$2
93         # If there already is a directory at the submodule path,
94         # expect it to be empty (since that is the default checkout
95         # action) and try to remove it.
96         # Note: if $path is a symlink to a directory the test will
97         # succeed but the rmdir will fail. We might want to fix this.
98         if test -d "$path"
99         then
100                 rmdir "$path" 2>/dev/null ||
101                 die "Directory '$path' exist, but is neither empty nor a git repository"
102         fi
104         test -e "$path" &&
105         die "A file already exist at path '$path'"
107         git-clone -n "$url" "$path" ||
108         die "Clone of '$url' into submodule path '$path' failed"
112 # Add a new submodule to the working tree, .gitmodules and the index
114 # $@ = repo path
116 # optional branch is stored in global branch variable
118 cmd_add()
120         # parse $args after "submodule ... add".
121         while test $# -ne 0
122         do
123                 case "$1" in
124                 -b | --branch)
125                         case "$2" in '') usage ;; esac
126                         branch=$2
127                         shift
128                         ;;
129                 -q|--quiet)
130                         quiet=1
131                         ;;
132                 --)
133                         shift
134                         break
135                         ;;
136                 -*)
137                         usage
138                         ;;
139                 *)
140                         break
141                         ;;
142                 esac
143                 shift
144         done
146         repo=$1
147         path=$2
149         if test -z "$repo" -o -z "$path"; then
150                 usage
151         fi
153         # assure repo is absolute or relative to parent
154         case "$repo" in
155         ./*|../*)
156                 # dereference source url relative to parent's url
157                 realrepo=$(resolve_relative_url "$repo") || exit
158                 ;;
159         *:*|/*)
160                 # absolute url
161                 realrepo=$repo
162                 ;;
163         *)
164                 die "repo URL: '$repo' must be absolute or begin with ./|../"
165         ;;
166         esac
168         # strip trailing slashes from path
169         path=$(echo "$path" | sed -e 's|/*$||')
171         git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
172         die "'$path' already exists in the index"
174         # perhaps the path exists and is already a git repo, else clone it
175         if test -e "$path"
176         then
177                 if test -d "$path"/.git -o -f "$path"/.git
178                 then
179                         echo "Adding existing repo at '$path' to the index"
180                 else
181                         die "'$path' already exists and is not a valid git repo"
182                 fi
184                 case "$repo" in
185                 ./*|../*)
186                         url=$(resolve_relative_url "$repo") || exit
187                     ;;
188                 *)
189                         url="$repo"
190                         ;;
191                 esac
192                 git config submodule."$path".url "$url"
193         else
195                 module_clone "$path" "$realrepo" || exit
196                 (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
197                 die "Unable to checkout submodule '$path'"
198         fi
200         git add "$path" ||
201         die "Failed to add submodule '$path'"
203         git config -f .gitmodules submodule."$path".path "$path" &&
204         git config -f .gitmodules submodule."$path".url "$repo" &&
205         git add .gitmodules ||
206         die "Failed to register submodule '$path'"
210 # Execute an arbitrary command sequence in each checked out
211 # submodule
213 # $@ = command to execute
215 cmd_foreach()
217         module_list |
218         while read mode sha1 stage path
219         do
220                 if test -e "$path"/.git
221                 then
222                         say "Entering '$path'"
223                         (cd "$path" && eval "$@") ||
224                         die "Stopping at '$path'; script returned non-zero status."
225                 fi
226         done
230 # Register submodules in .git/config
232 # $@ = requested paths (default to all)
234 cmd_init()
236         # parse $args after "submodule ... init".
237         while test $# -ne 0
238         do
239                 case "$1" in
240                 -q|--quiet)
241                         quiet=1
242                         ;;
243                 --)
244                         shift
245                         break
246                         ;;
247                 -*)
248                         usage
249                         ;;
250                 *)
251                         break
252                         ;;
253                 esac
254                 shift
255         done
257         module_list "$@" |
258         while read mode sha1 stage path
259         do
260                 # Skip already registered paths
261                 name=$(module_name "$path") || exit
262                 url=$(git config submodule."$name".url)
263                 test -z "$url" || continue
265                 url=$(git config -f .gitmodules submodule."$name".url)
266                 test -z "$url" &&
267                 die "No url found for submodule path '$path' in .gitmodules"
269                 # Possibly a url relative to parent
270                 case "$url" in
271                 ./*|../*)
272                         url=$(resolve_relative_url "$url") || exit
273                         ;;
274                 esac
276                 git config submodule."$name".url "$url" ||
277                 die "Failed to register url for submodule path '$path'"
279                 say "Submodule '$name' ($url) registered for path '$path'"
280         done
284 # Update each submodule path to correct revision, using clone and checkout as needed
286 # $@ = requested paths (default to all)
288 cmd_update()
290         # parse $args after "submodule ... update".
291         while test $# -ne 0
292         do
293                 case "$1" in
294                 -q|--quiet)
295                         shift
296                         quiet=1
297                         ;;
298                 -i|--init)
299                         shift
300                         cmd_init "$@" || return
301                         ;;
302                 --)
303                         shift
304                         break
305                         ;;
306                 -*)
307                         usage
308                         ;;
309                 *)
310                         break
311                         ;;
312                 esac
313         done
315         module_list "$@" |
316         while read mode sha1 stage path
317         do
318                 name=$(module_name "$path") || exit
319                 url=$(git config submodule."$name".url)
320                 if test -z "$url"
321                 then
322                         # Only mention uninitialized submodules when its
323                         # path have been specified
324                         test "$#" != "0" &&
325                         say "Submodule path '$path' not initialized"
326                         say "Maybe you want to use 'update --init'?"
327                         continue
328                 fi
330                 if ! test -d "$path"/.git -o -f "$path"/.git
331                 then
332                         module_clone "$path" "$url" || exit
333                         subsha1=
334                 else
335                         subsha1=$(unset GIT_DIR; cd "$path" &&
336                                 git rev-parse --verify HEAD) ||
337                         die "Unable to find current revision in submodule path '$path'"
338                 fi
340                 if test "$subsha1" != "$sha1"
341                 then
342                         (unset GIT_DIR; cd "$path" && git-fetch &&
343                                 git-checkout -q "$sha1") ||
344                         die "Unable to checkout '$sha1' in submodule path '$path'"
346                         say "Submodule path '$path': checked out '$sha1'"
347                 fi
348         done
351 set_name_rev () {
352         revname=$( (
353                 unset GIT_DIR
354                 cd "$1" && {
355                         git describe "$2" 2>/dev/null ||
356                         git describe --tags "$2" 2>/dev/null ||
357                         git describe --contains "$2" 2>/dev/null ||
358                         git describe --all --always "$2"
359                 }
360         ) )
361         test -z "$revname" || revname=" ($revname)"
364 # Show commit summary for submodules in index or working tree
366 # If '--cached' is given, show summary between index and given commit,
367 # or between working tree and given commit
369 # $@ = [commit (default 'HEAD'),] requested paths (default all)
371 cmd_summary() {
372         summary_limit=-1
373         for_status=
375         # parse $args after "submodule ... summary".
376         while test $# -ne 0
377         do
378                 case "$1" in
379                 --cached)
380                         cached="$1"
381                         ;;
382                 --for-status)
383                         for_status="$1"
384                         ;;
385                 -n|--summary-limit)
386                         if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2"
387                         then
388                                 :
389                         else
390                                 usage
391                         fi
392                         shift
393                         ;;
394                 --)
395                         shift
396                         break
397                         ;;
398                 -*)
399                         usage
400                         ;;
401                 *)
402                         break
403                         ;;
404                 esac
405                 shift
406         done
408         test $summary_limit = 0 && return
410         if rev=$(git rev-parse --verify "$1^0" 2>/dev/null)
411         then
412                 head=$rev
413                 shift
414         else
415                 head=HEAD
416         fi
418         cd_to_toplevel
419         # Get modified modules cared by user
420         modules=$(git diff-index $cached --raw $head -- "$@" |
421                 grep -e '^:160000' -e '^:[0-7]* 160000' |
422                 while read mod_src mod_dst sha1_src sha1_dst status name
423                 do
424                         # Always show modules deleted or type-changed (blob<->module)
425                         test $status = D -o $status = T && echo "$name" && continue
426                         # Also show added or modified modules which are checked out
427                         GIT_DIR="$name/.git" git-rev-parse --git-dir >/dev/null 2>&1 &&
428                         echo "$name"
429                 done
430         )
432         test -z "$modules" && return
434         git diff-index $cached --raw $head -- $modules |
435         grep -e '^:160000' -e '^:[0-7]* 160000' |
436         cut -c2- |
437         while read mod_src mod_dst sha1_src sha1_dst status name
438         do
439                 if test -z "$cached" &&
440                         test $sha1_dst = 0000000000000000000000000000000000000000
441                 then
442                         case "$mod_dst" in
443                         160000)
444                                 sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD)
445                                 ;;
446                         100644 | 100755 | 120000)
447                                 sha1_dst=$(git hash-object $name)
448                                 ;;
449                         000000)
450                                 ;; # removed
451                         *)
452                                 # unexpected type
453                                 echo >&2 "unexpected mode $mod_dst"
454                                 continue ;;
455                         esac
456                 fi
457                 missing_src=
458                 missing_dst=
460                 test $mod_src = 160000 &&
461                 ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_src^0 >/dev/null 2>&1 &&
462                 missing_src=t
464                 test $mod_dst = 160000 &&
465                 ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_dst^0 >/dev/null 2>&1 &&
466                 missing_dst=t
468                 total_commits=
469                 case "$missing_src,$missing_dst" in
470                 t,)
471                         errmsg="  Warn: $name doesn't contain commit $sha1_src"
472                         ;;
473                 ,t)
474                         errmsg="  Warn: $name doesn't contain commit $sha1_dst"
475                         ;;
476                 t,t)
477                         errmsg="  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
478                         ;;
479                 *)
480                         errmsg=
481                         total_commits=$(
482                         if test $mod_src = 160000 -a $mod_dst = 160000
483                         then
484                                 range="$sha1_src...$sha1_dst"
485                         elif test $mod_src = 160000
486                         then
487                                 range=$sha1_src
488                         else
489                                 range=$sha1_dst
490                         fi
491                         GIT_DIR="$name/.git" \
492                         git log --pretty=oneline --first-parent $range | wc -l
493                         )
494                         total_commits=" ($(($total_commits + 0)))"
495                         ;;
496                 esac
498                 sha1_abbr_src=$(echo $sha1_src | cut -c1-7)
499                 sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
500                 if test $status = T
501                 then
502                         if test $mod_dst = 160000
503                         then
504                                 echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
505                         else
506                                 echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
507                         fi
508                 else
509                         echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
510                 fi
511                 if test -n "$errmsg"
512                 then
513                         # Don't give error msg for modification whose dst is not submodule
514                         # i.e. deleted or changed to blob
515                         test $mod_dst = 160000 && echo "$errmsg"
516                 else
517                         if test $mod_src = 160000 -a $mod_dst = 160000
518                         then
519                                 limit=
520                                 test $summary_limit -gt 0 && limit="-$summary_limit"
521                                 GIT_DIR="$name/.git" \
522                                 git log $limit --pretty='format:  %m %s' \
523                                 --first-parent $sha1_src...$sha1_dst
524                         elif test $mod_dst = 160000
525                         then
526                                 GIT_DIR="$name/.git" \
527                                 git log --pretty='format:  > %s' -1 $sha1_dst
528                         else
529                                 GIT_DIR="$name/.git" \
530                                 git log --pretty='format:  < %s' -1 $sha1_src
531                         fi
532                         echo
533                 fi
534                 echo
535         done |
536         if test -n "$for_status"; then
537                 echo "# Modified submodules:"
538                 echo "#"
539                 sed -e 's|^|# |' -e 's|^# $|#|'
540         else
541                 cat
542         fi
545 # List all submodules, prefixed with:
546 #  - submodule not initialized
547 #  + different revision checked out
549 # If --cached was specified the revision in the index will be printed
550 # instead of the currently checked out revision.
552 # $@ = requested paths (default to all)
554 cmd_status()
556         # parse $args after "submodule ... status".
557         while test $# -ne 0
558         do
559                 case "$1" in
560                 -q|--quiet)
561                         quiet=1
562                         ;;
563                 --cached)
564                         cached=1
565                         ;;
566                 --)
567                         shift
568                         break
569                         ;;
570                 -*)
571                         usage
572                         ;;
573                 *)
574                         break
575                         ;;
576                 esac
577                 shift
578         done
580         module_list "$@" |
581         while read mode sha1 stage path
582         do
583                 name=$(module_name "$path") || exit
584                 url=$(git config submodule."$name".url)
585                 if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
586                 then
587                         say "-$sha1 $path"
588                         continue;
589                 fi
590                 set_name_rev "$path" "$sha1"
591                 if git diff-files --quiet -- "$path"
592                 then
593                         say " $sha1 $path$revname"
594                 else
595                         if test -z "$cached"
596                         then
597                                 sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
598                                 set_name_rev "$path" "$sha1"
599                         fi
600                         say "+$sha1 $path$revname"
601                 fi
602         done
605 # This loop parses the command line arguments to find the
606 # subcommand name to dispatch.  Parsing of the subcommand specific
607 # options are primarily done by the subcommand implementations.
608 # Subcommand specific options such as --branch and --cached are
609 # parsed here as well, for backward compatibility.
611 while test $# != 0 && test -z "$command"
612 do
613         case "$1" in
614         add | foreach | init | update | status | summary)
615                 command=$1
616                 ;;
617         -q|--quiet)
618                 quiet=1
619                 ;;
620         -b|--branch)
621                 case "$2" in
622                 '')
623                         usage
624                         ;;
625                 esac
626                 branch="$2"; shift
627                 ;;
628         --cached)
629                 cached="$1"
630                 ;;
631         --)
632                 break
633                 ;;
634         -*)
635                 usage
636                 ;;
637         *)
638                 break
639                 ;;
640         esac
641         shift
642 done
644 # No command word defaults to "status"
645 test -n "$command" || command=status
647 # "-b branch" is accepted only by "add"
648 if test -n "$branch" && test "$command" != add
649 then
650         usage
651 fi
653 # "--cached" is accepted only by "status" and "summary"
654 if test -n "$cached" && test "$command" != status -a "$command" != summary
655 then
656         usage
657 fi
659 "cmd_$command" "$@"