Code

git-submodule summary: limit summary size
[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]|status|init|update|summary [-n|--summary-limit <n>] [<commit>]] \
9 [--] [<path>...]"
10 OPTIONS_SPEC=
11 . git-sh-setup
12 require_work_tree
14 command=
15 branch=
16 quiet=
17 cached=
19 #
20 # print stuff on stdout unless -q was specified
21 #
22 say()
23 {
24         if test -z "$quiet"
25         then
26                 echo "$@"
27         fi
28 }
30 # NEEDSWORK: identical function exists in get_repo_base in clone.sh
31 get_repo_base() {
32         (
33                 cd "`/bin/pwd`" &&
34                 cd "$1" || cd "$1.git" &&
35                 {
36                         cd .git
37                         pwd
38                 }
39         ) 2>/dev/null
40 }
42 # Resolve relative url by appending to parent's url
43 resolve_relative_url ()
44 {
45         branch="$(git symbolic-ref HEAD 2>/dev/null)"
46         remote="$(git config branch.${branch#refs/heads/}.remote)"
47         remote="${remote:-origin}"
48         remoteurl="$(git config remote.$remote.url)" ||
49                 die "remote ($remote) does not have a url in .git/config"
50         url="$1"
51         while test -n "$url"
52         do
53                 case "$url" in
54                 ../*)
55                         url="${url#../}"
56                         remoteurl="${remoteurl%/*}"
57                         ;;
58                 ./*)
59                         url="${url#./}"
60                         ;;
61                 *)
62                         break;;
63                 esac
64         done
65         echo "$remoteurl/$url"
66 }
68 #
69 # Map submodule path to submodule name
70 #
71 # $1 = path
72 #
73 module_name()
74 {
75         # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
76         re=$(printf '%s' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
77         name=$( GIT_CONFIG=.gitmodules \
78                 git config --get-regexp '^submodule\..*\.path$' |
79                 sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
80        test -z "$name" &&
81        die "No submodule mapping found in .gitmodules for path '$path'"
82        echo "$name"
83 }
85 #
86 # Clone a submodule
87 #
88 # Prior to calling, cmd_update checks that a possibly existing
89 # path is not a git repository.
90 # Likewise, cmd_add checks that path does not exist at all,
91 # since it is the location of a new submodule.
92 #
93 module_clone()
94 {
95         path=$1
96         url=$2
98         # If there already is a directory at the submodule path,
99         # expect it to be empty (since that is the default checkout
100         # action) and try to remove it.
101         # Note: if $path is a symlink to a directory the test will
102         # succeed but the rmdir will fail. We might want to fix this.
103         if test -d "$path"
104         then
105                 rmdir "$path" 2>/dev/null ||
106                 die "Directory '$path' exist, but is neither empty nor a git repository"
107         fi
109         test -e "$path" &&
110         die "A file already exist at path '$path'"
112         git-clone -n "$url" "$path" ||
113         die "Clone of '$url' into submodule path '$path' failed"
117 # Add a new submodule to the working tree, .gitmodules and the index
119 # $@ = repo [path]
121 # optional branch is stored in global branch variable
123 cmd_add()
125         # parse $args after "submodule ... add".
126         while test $# -ne 0
127         do
128                 case "$1" in
129                 -b | --branch)
130                         case "$2" in '') usage ;; esac
131                         branch=$2
132                         shift
133                         ;;
134                 -q|--quiet)
135                         quiet=1
136                         ;;
137                 --)
138                         shift
139                         break
140                         ;;
141                 -*)
142                         usage
143                         ;;
144                 *)
145                         break
146                         ;;
147                 esac
148                 shift
149         done
151         repo=$1
152         path=$2
154         if test -z "$repo"; then
155                 usage
156         fi
158         case "$repo" in
159         ./*|../*)
160                 # dereference source url relative to parent's url
161                 realrepo="$(resolve_relative_url $repo)" ;;
162         *)
163                 # Turn the source into an absolute path if
164                 # it is local
165                 if base=$(get_repo_base "$repo"); then
166                         repo="$base"
167                 fi
168                 realrepo=$repo
169                 ;;
170         esac
172         # Guess path from repo if not specified or strip trailing slashes
173         if test -z "$path"; then
174                 path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
175         else
176                 path=$(echo "$path" | sed -e 's|/*$||')
177         fi
179         test -e "$path" &&
180         die "'$path' already exists"
182         git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
183         die "'$path' already exists in the index"
185         module_clone "$path" "$realrepo" || exit
186         (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
187         die "Unable to checkout submodule '$path'"
188         git add "$path" ||
189         die "Failed to add submodule '$path'"
191         GIT_CONFIG=.gitmodules git config submodule."$path".path "$path" &&
192         GIT_CONFIG=.gitmodules git config submodule."$path".url "$repo" &&
193         git add .gitmodules ||
194         die "Failed to register submodule '$path'"
198 # Register submodules in .git/config
200 # $@ = requested paths (default to all)
202 cmd_init()
204         # parse $args after "submodule ... init".
205         while test $# -ne 0
206         do
207                 case "$1" in
208                 -q|--quiet)
209                         quiet=1
210                         ;;
211                 --)
212                         shift
213                         break
214                         ;;
215                 -*)
216                         usage
217                         ;;
218                 *)
219                         break
220                         ;;
221                 esac
222                 shift
223         done
225         git ls-files --stage -- "$@" | grep -e '^160000 ' |
226         while read mode sha1 stage path
227         do
228                 # Skip already registered paths
229                 name=$(module_name "$path") || exit
230                 url=$(git config submodule."$name".url)
231                 test -z "$url" || continue
233                 url=$(GIT_CONFIG=.gitmodules git config submodule."$name".url)
234                 test -z "$url" &&
235                 die "No url found for submodule path '$path' in .gitmodules"
237                 # Possibly a url relative to parent
238                 case "$url" in
239                 ./*|../*)
240                         url="$(resolve_relative_url "$url")"
241                         ;;
242                 esac
244                 git config submodule."$name".url "$url" ||
245                 die "Failed to register url for submodule path '$path'"
247                 say "Submodule '$name' ($url) registered for path '$path'"
248         done
252 # Update each submodule path to correct revision, using clone and checkout as needed
254 # $@ = requested paths (default to all)
256 cmd_update()
258         # parse $args after "submodule ... update".
259         while test $# -ne 0
260         do
261                 case "$1" in
262                 -q|--quiet)
263                         quiet=1
264                         ;;
265                 --)
266                         shift
267                         break
268                         ;;
269                 -*)
270                         usage
271                         ;;
272                 *)
273                         break
274                         ;;
275                 esac
276                 shift
277         done
279         git ls-files --stage -- "$@" | grep -e '^160000 ' |
280         while read mode sha1 stage path
281         do
282                 name=$(module_name "$path") || exit
283                 url=$(git config submodule."$name".url)
284                 if test -z "$url"
285                 then
286                         # Only mention uninitialized submodules when its
287                         # path have been specified
288                         test "$#" != "0" &&
289                         say "Submodule path '$path' not initialized"
290                         continue
291                 fi
293                 if ! test -d "$path"/.git
294                 then
295                         module_clone "$path" "$url" || exit
296                         subsha1=
297                 else
298                         subsha1=$(unset GIT_DIR; cd "$path" &&
299                                 git rev-parse --verify HEAD) ||
300                         die "Unable to find current revision in submodule path '$path'"
301                 fi
303                 if test "$subsha1" != "$sha1"
304                 then
305                         (unset GIT_DIR; cd "$path" && git-fetch &&
306                                 git-checkout -q "$sha1") ||
307                         die "Unable to checkout '$sha1' in submodule path '$path'"
309                         say "Submodule path '$path': checked out '$sha1'"
310                 fi
311         done
314 set_name_rev () {
315         revname=$( (
316                 unset GIT_DIR
317                 cd "$1" && {
318                         git describe "$2" 2>/dev/null ||
319                         git describe --tags "$2" 2>/dev/null ||
320                         git describe --contains --tags "$2"
321                 }
322         ) )
323         test -z "$revname" || revname=" ($revname)"
326 # Show commit summary for submodules in index or working tree
328 # If '--cached' is given, show summary between index and given commit,
329 # or between working tree and given commit
331 # $@ = [commit (default 'HEAD'),] requested paths (default all)
333 cmd_summary() {
334         summary_limit=-1
336         # parse $args after "submodule ... summary".
337         while test $# -ne 0
338         do
339                 case "$1" in
340                 --cached)
341                         cached="$1"
342                         ;;
343                 -n|--summary-limit)
344                         if summary_limit=$(($2 + 0)) 2>/dev/null && test "$summary_limit" = "$2"
345                         then
346                                 :
347                         else
348                                 usage
349                         fi
350                         shift
351                         ;;
352                 --)
353                         shift
354                         break
355                         ;;
356                 -*)
357                         usage
358                         ;;
359                 *)
360                         break
361                         ;;
362                 esac
363                 shift
364         done
366         test $summary_limit = 0 && return
368         if rev=$(git rev-parse --verify "$1^0" 2>/dev/null)
369         then
370                 head=$rev
371                 shift
372         else
373                 head=HEAD
374         fi
376         cd_to_toplevel
377         # Get modified modules cared by user
378         modules=$(git diff-index $cached --raw $head -- "$@" |
379                 grep -e '^:160000' -e '^:[0-7]* 160000' |
380                 while read mod_src mod_dst sha1_src sha1_dst status name
381                 do
382                         # Always show modules deleted or type-changed (blob<->module)
383                         test $status = D -o $status = T && echo "$name" && continue
384                         # Also show added or modified modules which are checked out
385                         GIT_DIR="$name/.git" git-rev-parse --git-dir >/dev/null 2>&1 &&
386                         echo "$name"
387                 done
388         )
390         test -n "$modules" &&
391         git diff-index $cached --raw $head -- $modules |
392         grep -e '^:160000' -e '^:[0-7]* 160000' |
393         cut -c2- |
394         while read mod_src mod_dst sha1_src sha1_dst status name
395         do
396                 if test -z "$cached" &&
397                         test $sha1_dst = 0000000000000000000000000000000000000000
398                 then
399                         case "$mod_dst" in
400                         160000)
401                                 sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD)
402                                 ;;
403                         100644 | 100755 | 120000)
404                                 sha1_dst=$(git hash-object $name)
405                                 ;;
406                         000000)
407                                 ;; # removed
408                         *)
409                                 # unexpected type
410                                 echo >&2 "unexpected mode $mod_dst"
411                                 continue ;;
412                         esac
413                 fi
414                 missing_src=
415                 missing_dst=
417                 test $mod_src = 160000 &&
418                 ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_src^0 >/dev/null 2>&1 &&
419                 missing_src=t
421                 test $mod_dst = 160000 &&
422                 ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_dst^0 >/dev/null 2>&1 &&
423                 missing_dst=t
425                 total_commits=
426                 case "$missing_src,$missing_dst" in
427                 t,)
428                         errmsg="  Warn: $name doesn't contain commit $sha1_src"
429                         ;;
430                 ,t)
431                         errmsg="  Warn: $name doesn't contain commit $sha1_dst"
432                         ;;
433                 t,t)
434                         errmsg="  Warn: $name doesn't contain commits $sha1_src and $sha1_dst"
435                         ;;
436                 *)
437                         errmsg=
438                         total_commits=$(
439                         if test $mod_src = 160000 -a $mod_dst = 160000
440                         then
441                                 range="$sha1_src...$sha1_dst"
442                         elif test $mod_src = 160000
443                         then
444                                 range=$sha1_src
445                         else
446                                 range=$sha1_dst
447                         fi
448                         GIT_DIR="$name/.git" \
449                         git log --pretty=oneline --first-parent $range | wc -l
450                         )
451                         total_commits=" ($total_commits)"
452                         ;;
453                 esac
455                 sha1_abbr_src=$(echo $sha1_src | cut -c1-7)
456                 sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7)
457                 if test $status = T
458                 then
459                         if test $mod_dst = 160000
460                         then
461                                 echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:"
462                         else
463                                 echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:"
464                         fi
465                 else
466                         echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
467                 fi
468                 if test -n "$errmsg"
469                 then
470                         # Don't give error msg for modification whose dst is not submodule
471                         # i.e. deleted or changed to blob
472                         test $mod_dst = 160000 && echo "$errmsg"
473                 else
474                         if test $mod_src = 160000 -a $mod_dst = 160000
475                         then
476                                 limit=
477                                 test $summary_limit -gt 0 && limit="-$summary_limit"
478                                 GIT_DIR="$name/.git" \
479                                 git log $limit --pretty='format:  %m %s' \
480                                 --first-parent $sha1_src...$sha1_dst
481                         elif test $mod_dst = 160000
482                         then
483                                 GIT_DIR="$name/.git" \
484                                 git log --pretty='format:  > %s' -1 $sha1_dst
485                         else
486                                 GIT_DIR="$name/.git" \
487                                 git log --pretty='format:  < %s' -1 $sha1_src
488                         fi
489                         echo
490                 fi
491                 echo
492         done
495 # List all submodules, prefixed with:
496 #  - submodule not initialized
497 #  + different revision checked out
499 # If --cached was specified the revision in the index will be printed
500 # instead of the currently checked out revision.
502 # $@ = requested paths (default to all)
504 cmd_status()
506         # parse $args after "submodule ... status".
507         while test $# -ne 0
508         do
509                 case "$1" in
510                 -q|--quiet)
511                         quiet=1
512                         ;;
513                 --cached)
514                         cached=1
515                         ;;
516                 --)
517                         shift
518                         break
519                         ;;
520                 -*)
521                         usage
522                         ;;
523                 *)
524                         break
525                         ;;
526                 esac
527                 shift
528         done
530         git ls-files --stage -- "$@" | grep -e '^160000 ' |
531         while read mode sha1 stage path
532         do
533                 name=$(module_name "$path") || exit
534                 url=$(git config submodule."$name".url)
535                 if test -z "$url" || ! test -d "$path"/.git
536                 then
537                         say "-$sha1 $path"
538                         continue;
539                 fi
540                 set_name_rev "$path" "$sha1"
541                 if git diff-files --quiet -- "$path"
542                 then
543                         say " $sha1 $path$revname"
544                 else
545                         if test -z "$cached"
546                         then
547                                 sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
548                                 set_name_rev "$path" "$sha1"
549                         fi
550                         say "+$sha1 $path$revname"
551                 fi
552         done
555 # This loop parses the command line arguments to find the
556 # subcommand name to dispatch.  Parsing of the subcommand specific
557 # options are primarily done by the subcommand implementations.
558 # Subcommand specific options such as --branch and --cached are
559 # parsed here as well, for backward compatibility.
561 while test $# != 0 && test -z "$command"
562 do
563         case "$1" in
564         add | init | update | status | summary)
565                 command=$1
566                 ;;
567         -q|--quiet)
568                 quiet=1
569                 ;;
570         -b|--branch)
571                 case "$2" in
572                 '')
573                         usage
574                         ;;
575                 esac
576                 branch="$2"; shift
577                 ;;
578         --cached)
579                 cached="$1"
580                 ;;
581         --)
582                 break
583                 ;;
584         -*)
585                 usage
586                 ;;
587         *)
588                 break
589                 ;;
590         esac
591         shift
592 done
594 # No command word defaults to "status"
595 test -n "$command" || command=status
597 # "-b branch" is accepted only by "add"
598 if test -n "$branch" && test "$command" != add
599 then
600         usage
601 fi
603 # "--cached" is accepted only by "status" and "summary"
604 if test -n "$cached" && test "$command" != status -a "$command" != summary
605 then
606         usage
607 fi
609 "cmd_$command" "$@"