git submodule status: Add --recursive to recurse into nested submodules
[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
6
7 dashless=$(basename "$0" | sed -e 's/-/ /')
8 USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> <path>
9    or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
10    or: $dashless [--quiet] init [--] [<path>...]
11    or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
12    or: $dashless [--quiet] summary [--cached] [--summary-limit <n>] [commit] [--] [<path>...]
13    or: $dashless [--quiet] foreach [--recursive] <command>
14    or: $dashless [--quiet] sync [--] [<path>...]"
15 OPTIONS_SPEC=
16 . git-sh-setup
17 . git-parse-remote
18 require_work_tree
19
20 command=
21 branch=
22 reference=
23 cached=
24 nofetch=
25 update=
26 prefix=
27
28 # Resolve relative url by appending to parent's url
29 resolve_relative_url ()
30 {
31         remote=$(get_default_remote)
32         remoteurl=$(git config "remote.$remote.url") ||
33                 die "remote ($remote) does not have a url defined in .git/config"
34         url="$1"
35         remoteurl=${remoteurl%/}
36         while test -n "$url"
37         do
38                 case "$url" in
39                 ../*)
40                         url="${url#../}"
41                         remoteurl="${remoteurl%/*}"
42                         ;;
43                 ./*)
44                         url="${url#./}"
45                         ;;
46                 *)
47                         break;;
48                 esac
49         done
50         echo "$remoteurl/${url%/}"
51 }
52
53 #
54 # Get submodule info for registered submodules
55 # $@ = path to limit submodule list
56 #
57 module_list()
58 {
59         git ls-files --error-unmatch --stage -- "$@" | grep '^160000 '
60 }
61
62 #
63 # Map submodule path to submodule name
64 #
65 # $1 = path
66 #
67 module_name()
68 {
69         # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
70         re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
71         name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
72                 sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
73        test -z "$name" &&
74        die "No submodule mapping found in .gitmodules for path '$path'"
75        echo "$name"
76 }
77
78 #
79 # Clone a submodule
80 #
81 # Prior to calling, cmd_update checks that a possibly existing
82 # path is not a git repository.
83 # Likewise, cmd_add checks that path does not exist at all,
84 # since it is the location of a new submodule.
85 #
86 module_clone()
87 {
88         path=$1
89         url=$2
90         reference="$3"
91
92         # If there already is a directory at the submodule path,
93         # expect it to be empty (since that is the default checkout
94         # action) and try to remove it.
95         # Note: if $path is a symlink to a directory the test will
96         # succeed but the rmdir will fail. We might want to fix this.
97         if test -d "$path"
98         then
99                 rmdir "$path" 2>/dev/null ||
100                 die "Directory '$path' exist, but is neither empty nor a git repository"
101         fi
102
103         test -e "$path" &&
104         die "A file already exist at path '$path'"
105
106         if test -n "$reference"
107         then
108                 git-clone "$reference" -n "$url" "$path"
109         else
110                 git-clone -n "$url" "$path"
111         fi ||
112         die "Clone of '$url' into submodule path '$path' failed"
113 }
114
115 #
116 # Add a new submodule to the working tree, .gitmodules and the index
117 #
118 # $@ = repo path
119 #
120 # optional branch is stored in global branch variable
121 #
122 cmd_add()
123 {
124         # parse $args after "submodule ... add".
125         while test $# -ne 0
126         do
127                 case "$1" in
128                 -b | --branch)
129                         case "$2" in '') usage ;; esac
130                         branch=$2
131                         shift
132                         ;;
133                 -q|--quiet)
134                         GIT_QUIET=1
135                         ;;
136                 --reference)
137                         case "$2" in '') usage ;; esac
138                         reference="--reference=$2"
139                         shift
140                         ;;
141                 --reference=*)
142                         reference="$1"
143                         shift
144                         ;;
145                 --)
146                         shift
147                         break
148                         ;;
149                 -*)
150                         usage
151                         ;;
152                 *)
153                         break
154                         ;;
155                 esac
156                 shift
157         done
158
159         repo=$1
160         path=$2
161
162         if test -z "$repo" -o -z "$path"; then
163                 usage
164         fi
165
166         # assure repo is absolute or relative to parent
167         case "$repo" in
168         ./*|../*)
169                 # dereference source url relative to parent's url
170                 realrepo=$(resolve_relative_url "$repo") || exit
171                 ;;
172         *:*|/*)
173                 # absolute url
174                 realrepo=$repo
175                 ;;
176         *)
177                 die "repo URL: '$repo' must be absolute or begin with ./|../"
178         ;;
179         esac
180
181         # normalize path:
182         # multiple //; leading ./; /./; /../; trailing /
183         path=$(printf '%s/\n' "$path" |
184                 sed -e '
185                         s|//*|/|g
186                         s|^\(\./\)*||
187                         s|/\./|/|g
188                         :start
189                         s|\([^/]*\)/\.\./||
190                         tstart
191                         s|/*$||
192                 ')
193         git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
194         die "'$path' already exists in the index"
195
196         # perhaps the path exists and is already a git repo, else clone it
197         if test -e "$path"
198         then
199                 if test -d "$path"/.git -o -f "$path"/.git
200                 then
201                         echo "Adding existing repo at '$path' to the index"
202                 else
203                         die "'$path' already exists and is not a valid git repo"
204                 fi
205
206                 case "$repo" in
207                 ./*|../*)
208                         url=$(resolve_relative_url "$repo") || exit
209                     ;;
210                 *)
211                         url="$repo"
212                         ;;
213                 esac
214                 git config submodule."$path".url "$url"
215         else
216
217                 module_clone "$path" "$realrepo" "$reference" || exit
218                 (
219                         unset GIT_DIR
220                         cd "$path" &&
221                         # ash fails to wordsplit ${branch:+-b "$branch"...}
222                         case "$branch" in
223                         '') git checkout -f -q ;;
224                         ?*) git checkout -f -q -b "$branch" "origin/$branch" ;;
225                         esac
226                 ) || die "Unable to checkout submodule '$path'"
227         fi
228
229         git add "$path" ||
230         die "Failed to add submodule '$path'"
231
232         git config -f .gitmodules submodule."$path".path "$path" &&
233         git config -f .gitmodules submodule."$path".url "$repo" &&
234         git add .gitmodules ||
235         die "Failed to register submodule '$path'"
236 }
237
238 #
239 # Execute an arbitrary command sequence in each checked out
240 # submodule
241 #
242 # $@ = command to execute
243 #
244 cmd_foreach()
245 {
246         # parse $args after "submodule ... foreach".
247         while test $# -ne 0
248         do
249                 case "$1" in
250                 -q|--quiet)
251                         GIT_QUIET=1
252                         ;;
253                 --recursive)
254                         recursive=1
255                         ;;
256                 -*)
257                         usage
258                         ;;
259                 *)
260                         break
261                         ;;
262                 esac
263                 shift
264         done
265
266         module_list |
267         while read mode sha1 stage path
268         do
269                 if test -e "$path"/.git
270                 then
271                         say "Entering '$prefix$path'"
272                         name=$(module_name "$path")
273                         (
274                                 prefix="$prefix$path/"
275                                 unset GIT_DIR
276                                 cd "$path" &&
277                                 eval "$@" &&
278                                 if test -n "$recursive"
279                                 then
280                                         cmd_foreach "--recursive" "$@"
281                                 fi
282                         ) ||
283                         die "Stopping at '$path'; script returned non-zero status."
284                 fi
285         done
286 }
287
288 #
289 # Register submodules in .git/config
290 #
291 # $@ = requested paths (default to all)
292 #
293 cmd_init()
294 {
295         # parse $args after "submodule ... init".
296         while test $# -ne 0
297         do
298                 case "$1" in
299                 -q|--quiet)
300                         GIT_QUIET=1
301                         ;;
302                 --)
303                         shift
304                         break
305                         ;;
306                 -*)
307                         usage
308                         ;;
309                 *)
310                         break
311                         ;;
312                 esac
313                 shift
314         done
315
316         module_list "$@" |
317         while read mode sha1 stage path
318         do
319                 # Skip already registered paths
320                 name=$(module_name "$path") || exit
321                 url=$(git config submodule."$name".url)
322                 test -z "$url" || continue
323
324                 url=$(git config -f .gitmodules submodule."$name".url)
325                 test -z "$url" &&
326                 die "No url found for submodule path '$path' in .gitmodules"
327
328                 # Possibly a url relative to parent
329                 case "$url" in
330                 ./*|../*)
331                         url=$(resolve_relative_url "$url") || exit
332                         ;;
333                 esac
334
335                 git config submodule."$name".url "$url" ||
336                 die "Failed to register url for submodule path '$path'"
337
338                 upd="$(git config -f .gitmodules submodule."$name".update)"
339                 test -z "$upd" ||
340                 git config submodule."$name".update "$upd" ||
341                 die "Failed to register update mode for submodule path '$path'"
342
343                 say "Submodule '$name' ($url) registered for path '$path'"
344         done
345 }
346
347 #
348 # Update each submodule path to correct revision, using clone and checkout as needed
349 #
350 # $@ = requested paths (default to all)
351 #
352 cmd_update()
353 {
354         # parse $args after "submodule ... update".
355         orig_args="$@"
356         while test $# -ne 0
357         do
358                 case "$1" in
359                 -q|--quiet)
360                         shift
361                         GIT_QUIET=1
362                         ;;
363                 -i|--init)
364                         init=1
365                         shift
366                         ;;
367                 -N|--no-fetch)
368                         shift
369                         nofetch=1
370                         ;;
371                 -r|--rebase)
372                         shift
373                         update="rebase"
374                         ;;
375                 --reference)
376                         case "$2" in '') usage ;; esac
377                         reference="--reference=$2"
378                         shift 2
379                         ;;
380                 --reference=*)
381                         reference="$1"
382                         shift
383                         ;;
384                 -m|--merge)
385                         shift
386                         update="merge"
387                         ;;
388                 --recursive)
389                         shift
390                         recursive=1
391                         ;;
392                 --)
393                         shift
394                         break
395                         ;;
396                 -*)
397                         usage
398                         ;;
399                 *)
400                         break
401                         ;;
402                 esac
403         done
404
405         if test -n "$init"
406         then
407                 cmd_init "--" "$@" || return
408         fi
409
410         module_list "$@" |
411         while read mode sha1 stage path
412         do
413                 name=$(module_name "$path") || exit
414                 url=$(git config submodule."$name".url)
415                 update_module=$(git config submodule."$name".update)
416                 if test -z "$url"
417                 then
418                         # Only mention uninitialized submodules when its
419                         # path have been specified
420                         test "$#" != "0" &&
421                         say "Submodule path '$path' not initialized" &&
422                         say "Maybe you want to use 'update --init'?"
423                         continue
424                 fi
425
426                 if ! test -d "$path"/.git -o -f "$path"/.git
427                 then
428                         module_clone "$path" "$url" "$reference"|| exit
429                         subsha1=
430                 else
431                         subsha1=$(unset GIT_DIR; cd "$path" &&
432                                 git rev-parse --verify HEAD) ||
433                         die "Unable to find current revision in submodule path '$path'"
434                 fi
435
436                 if ! test -z "$update"
437                 then
438                         update_module=$update
439                 fi
440
441                 if test "$subsha1" != "$sha1"
442                 then
443                         force=
444                         if test -z "$subsha1"
445                         then
446                                 force="-f"
447                         fi
448
449                         if test -z "$nofetch"
450                         then
451                                 (unset GIT_DIR; cd "$path" &&
452                                         git-fetch) ||
453                                 die "Unable to fetch in submodule path '$path'"
454                         fi
455
456                         case "$update_module" in
457                         rebase)
458                                 command="git rebase"
459                                 action="rebase"
460                                 msg="rebased onto"
461                                 ;;
462                         merge)
463                                 command="git merge"
464                                 action="merge"
465                                 msg="merged in"
466                                 ;;
467                         *)
468                                 command="git checkout $force -q"
469                                 action="checkout"
470                                 msg="checked out"
471                                 ;;
472                         esac
473
474                         (unset GIT_DIR; cd "$path" && $command "$sha1") ||
475                         die "Unable to $action '$sha1' in submodule path '$path'"
476                         say "Submodule path '$path': $msg '$sha1'"
477                 fi
478
479                 if test -n "$recursive"
480                 then
481                         (unset GIT_DIR; cd "$path" && cmd_update $orig_args) ||
482                         die "Failed to recurse into submodule path '$path'"
483                 fi
484         done
485 }
486
487 set_name_rev () {
488         revname=$( (
489                 unset GIT_DIR
490                 cd "$1" && {
491                         git describe "$2" 2>/dev/null ||
492                         git describe --tags "$2" 2>/dev/null ||
493                         git describe --contains "$2" 2>/dev/null ||
494                         git describe --all --always "$2"
495                 }
496         ) )
497         test -z "$revname" || revname=" ($revname)"
498 }
499 #
500 # Show commit summary for submodules in index or working tree
501 #
502 # If '--cached' is given, show summary between index and given commit,
503 # or between working tree and given commit
504 #
505 # $@ = [commit (default 'HEAD'),] requested paths (default all)
506 #
507 cmd_summary() {
508         summary_limit=-1
509         for_status=
510
511         # parse $args after "submodule ... summary".
512         while test $# -ne 0
513         do
514                 case "$1" in
515                 --cached)
516                         cached="$1"
517                         ;;
518                 --for-status)
519                         for_status="$1"
520                         ;;
521                 -n|--summary-limit)
522                         if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2"
523                         then
524                                 :
525                         else
526                                 usage
527                         fi
528                         shift
529                         ;;
530                 --)
531                         shift
532                         break
533                         ;;
534                 -*)
535                         usage
536                         ;;
537                 *)
538                         break
539                         ;;
540                 esac
541                 shift
542         done
543
544         test $summary_limit = 0 && return
545
546         if rev=$(git rev-parse -q --verify "$1^0")
547         then
548                 head=$rev
549                 shift
550         else
551                 head=HEAD
552         fi
553
554         cd_to_toplevel
555         # Get modified modules cared by user
556         modules=$(git diff-index $cached --raw $head -- "$@" |
557                 egrep '^:([0-7]* )?160000' |
558                 while read mod_src mod_dst sha1_src sha1_dst status name
559                 do
560                         # Always show modules deleted or type-changed (blob<->module)
561                         test $status = D -o $status = T && echo "$name" && continue
562                         # Also show added or modified modules which are checked out
563                         GIT_DIR="$name/.git" git-rev-parse --git-dir >/dev/null 2>&1 &&
564                         echo "$name"
565                 done
566         )
567
568         test -z "$modules" && return
569
570         git diff-index $cached --raw $head -- $modules |
571         egrep '^:([0-7]* )?160000' |
572         cut -c2- |
573         while read mod_src mod_dst sha1_src sha1_dst status name
574         do
575                 if test -z "$cached" &&
576                         test $sha1_dst = 0000000000000000000000000000000000000000
577                 then
578                         case "$mod_dst" in
579                         160000)
580                                 sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD)
581                                 ;;
582                         100644 | 100755 | 120000)
583                                 sha1_dst=$(git hash-object $name)
584                                 ;;
585                         000000)
586                                 ;; # removed
587                         *)
588                                 # unexpected type
589                                 echo >&2 "unexpected mode $mod_dst"
590                                 continue ;;
591                         esac
592                 fi
593                 missing_src=
594                 missing_dst=
595
596                 test $mod_src = 160000 &&
597                 ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_src^0 >/dev/null &&
598                 missing_src=t
599
600                 test $mod_dst = 160000 &&
601                 ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
602                 missing_dst=t
603
604                 total_commits=
605                 case "$missing_src,$missing_dst" in
606                 t,)
607                         errmsg="  Warn: $name doesn't contain commit $sha1_src"
608                         ;;
609                 ,t)
610                         errmsg="  Warn: $name doesn't contain commit $sha1_dst"
611                         ;;
612                 t,t)
613                         errmsg="  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
614                         ;;
615                 *)
616                         errmsg=
617                         total_commits=$(
618                         if test $mod_src = 160000 -a $mod_dst = 160000
619                         then
620                                 range="$sha1_src...$sha1_dst"
621                         elif test $mod_src = 160000
622                         then
623                                 range=$sha1_src
624                         else
625                                 range=$sha1_dst
626                         fi
627                         GIT_DIR="$name/.git" \
628                         git log --pretty=oneline --first-parent $range | wc -l
629                         )
630                         total_commits=" ($(($total_commits + 0)))"
631                         ;;
632                 esac
633
634                 sha1_abbr_src=$(echo $sha1_src | cut -c1-7)
635                 sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
636                 if test $status = T
637                 then
638                         if test $mod_dst = 160000
639                         then
640                                 echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
641                         else
642                                 echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
643                         fi
644                 else
645                         echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
646                 fi
647                 if test -n "$errmsg"
648                 then
649                         # Don't give error msg for modification whose dst is not submodule
650                         # i.e. deleted or changed to blob
651                         test $mod_dst = 160000 && echo "$errmsg"
652                 else
653                         if test $mod_src = 160000 -a $mod_dst = 160000
654                         then
655                                 limit=
656                                 test $summary_limit -gt 0 && limit="-$summary_limit"
657                                 GIT_DIR="$name/.git" \
658                                 git log $limit --pretty='format:  %m %s' \
659                                 --first-parent $sha1_src...$sha1_dst
660                         elif test $mod_dst = 160000
661                         then
662                                 GIT_DIR="$name/.git" \
663                                 git log --pretty='format:  > %s' -1 $sha1_dst
664                         else
665                                 GIT_DIR="$name/.git" \
666                                 git log --pretty='format:  < %s' -1 $sha1_src
667                         fi
668                         echo
669                 fi
670                 echo
671         done |
672         if test -n "$for_status"; then
673                 echo "# Modified submodules:"
674                 echo "#"
675                 sed -e 's|^|# |' -e 's|^# $|#|'
676         else
677                 cat
678         fi
679 }
680 #
681 # List all submodules, prefixed with:
682 #  - submodule not initialized
683 #  + different revision checked out
684 #
685 # If --cached was specified the revision in the index will be printed
686 # instead of the currently checked out revision.
687 #
688 # $@ = requested paths (default to all)
689 #
690 cmd_status()
691 {
692         # parse $args after "submodule ... status".
693         orig_args="$@"
694         while test $# -ne 0
695         do
696                 case "$1" in
697                 -q|--quiet)
698                         GIT_QUIET=1
699                         ;;
700                 --cached)
701                         cached=1
702                         ;;
703                 --recursive)
704                         recursive=1
705                         ;;
706                 --)
707                         shift
708                         break
709                         ;;
710                 -*)
711                         usage
712                         ;;
713                 *)
714                         break
715                         ;;
716                 esac
717                 shift
718         done
719
720         module_list "$@" |
721         while read mode sha1 stage path
722         do
723                 name=$(module_name "$path") || exit
724                 url=$(git config submodule."$name".url)
725                 displaypath="$prefix$path"
726                 if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git
727                 then
728                         say "-$sha1 $displaypath"
729                         continue;
730                 fi
731                 set_name_rev "$path" "$sha1"
732                 if git diff-files --quiet -- "$path"
733                 then
734                         say " $sha1 $displaypath$revname"
735                 else
736                         if test -z "$cached"
737                         then
738                                 sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
739                                 set_name_rev "$path" "$sha1"
740                         fi
741                         say "+$sha1 $displaypath$revname"
742                 fi
743
744                 if test -n "$recursive"
745                 then
746                         (
747                                 prefix="$displaypath/"
748                                 unset GIT_DIR
749                                 cd "$path" &&
750                                 cmd_status $orig_args
751                         ) ||
752                         die "Failed to recurse into submodule path '$path'"
753                 fi
754         done
755 }
756 #
757 # Sync remote urls for submodules
758 # This makes the value for remote.$remote.url match the value
759 # specified in .gitmodules.
760 #
761 cmd_sync()
762 {
763         while test $# -ne 0
764         do
765                 case "$1" in
766                 -q|--quiet)
767                         GIT_QUIET=1
768                         shift
769                         ;;
770                 --)
771                         shift
772                         break
773                         ;;
774                 -*)
775                         usage
776                         ;;
777                 *)
778                         break
779                         ;;
780                 esac
781         done
782         cd_to_toplevel
783         module_list "$@" |
784         while read mode sha1 stage path
785         do
786                 name=$(module_name "$path")
787                 url=$(git config -f .gitmodules --get submodule."$name".url)
788
789                 # Possibly a url relative to parent
790                 case "$url" in
791                 ./*|../*)
792                         url=$(resolve_relative_url "$url") || exit
793                         ;;
794                 esac
795
796                 if test -e "$path"/.git
797                 then
798                 (
799                         unset GIT_DIR
800                         cd "$path"
801                         remote=$(get_default_remote)
802                         say "Synchronizing submodule url for '$name'"
803                         git config remote."$remote".url "$url"
804                 )
805                 fi
806         done
807 }
808
809 # This loop parses the command line arguments to find the
810 # subcommand name to dispatch.  Parsing of the subcommand specific
811 # options are primarily done by the subcommand implementations.
812 # Subcommand specific options such as --branch and --cached are
813 # parsed here as well, for backward compatibility.
814
815 while test $# != 0 && test -z "$command"
816 do
817         case "$1" in
818         add | foreach | init | update | status | summary | sync)
819                 command=$1
820                 ;;
821         -q|--quiet)
822                 GIT_QUIET=1
823                 ;;
824         -b|--branch)
825                 case "$2" in
826                 '')
827                         usage
828                         ;;
829                 esac
830                 branch="$2"; shift
831                 ;;
832         --cached)
833                 cached="$1"
834                 ;;
835         --)
836                 break
837                 ;;
838         -*)
839                 usage
840                 ;;
841         *)
842                 break
843                 ;;
844         esac
845         shift
846 done
847
848 # No command word defaults to "status"
849 test -n "$command" || command=status
850
851 # "-b branch" is accepted only by "add"
852 if test -n "$branch" && test "$command" != add
853 then
854         usage
855 fi
856
857 # "--cached" is accepted only by "status" and "summary"
858 if test -n "$cached" && test "$command" != status -a "$command" != summary
859 then
860         usage
861 fi
862
863 "cmd_$command" "$@"