4d28fc0988f1359e914cd27e3750cf01f223527e
   1#!/bin/sh
   2#
   3# git-submodules.sh: add, init, update or list git submodules
   4#
   5# Copyright (c) 2007 Lars Hjemli
   6
   7USAGE="[--quiet] [--cached] \
   8[add <repo> [-b branch]|status|init|update|summary [-n|--summary-limit <n>] [<commit>]] \
   9[--] [<path>...]"
  10OPTIONS_SPEC=
  11. git-sh-setup
  12require_work_tree
  13
  14command=
  15branch=
  16quiet=
  17cached=
  18
  19#
  20# print stuff on stdout unless -q was specified
  21#
  22say()
  23{
  24        if test -z "$quiet"
  25        then
  26                echo "$@"
  27        fi
  28}
  29
  30# NEEDSWORK: identical function exists in get_repo_base in clone.sh
  31get_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}
  41
  42# Resolve relative url by appending to parent's url
  43resolve_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}
  67
  68#
  69# Map submodule path to submodule name
  70#
  71# $1 = path
  72#
  73module_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}
  84
  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#
  93module_clone()
  94{
  95        path=$1
  96        url=$2
  97
  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
 108
 109        test -e "$path" &&
 110        die "A file already exist at path '$path'"
 111
 112        git-clone -n "$url" "$path" ||
 113        die "Clone of '$url' into submodule path '$path' failed"
 114}
 115
 116#
 117# Add a new submodule to the working tree, .gitmodules and the index
 118#
 119# $@ = repo [path]
 120#
 121# optional branch is stored in global branch variable
 122#
 123cmd_add()
 124{
 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
 150
 151        repo=$1
 152        path=$2
 153
 154        if test -z "$repo"; then
 155                usage
 156        fi
 157
 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
 171
 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
 178
 179        test -e "$path" &&
 180        die "'$path' already exists"
 181
 182        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
 183        die "'$path' already exists in the index"
 184
 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'"
 190
 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'"
 195}
 196
 197#
 198# Register submodules in .git/config
 199#
 200# $@ = requested paths (default to all)
 201#
 202cmd_init()
 203{
 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
 224
 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
 232
 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"
 236
 237                # Possibly a url relative to parent
 238                case "$url" in
 239                ./*|../*)
 240                        url="$(resolve_relative_url "$url")"
 241                        ;;
 242                esac
 243
 244                git config submodule."$name".url "$url" ||
 245                die "Failed to register url for submodule path '$path'"
 246
 247                say "Submodule '$name' ($url) registered for path '$path'"
 248        done
 249}
 250
 251#
 252# Update each submodule path to correct revision, using clone and checkout as needed
 253#
 254# $@ = requested paths (default to all)
 255#
 256cmd_update()
 257{
 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
 278
 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
 292
 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
 302
 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'"
 308
 309                        say "Submodule path '$path': checked out '$sha1'"
 310                fi
 311        done
 312}
 313
 314set_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)"
 324}
 325#
 326# Show commit summary for submodules in index or working tree
 327#
 328# If '--cached' is given, show summary between index and given commit,
 329# or between working tree and given commit
 330#
 331# $@ = [commit (default 'HEAD'),] requested paths (default all)
 332#
 333cmd_summary() {
 334        summary_limit=-1
 335
 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
 365
 366        test $summary_limit = 0 && return
 367
 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
 375
 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        )
 389
 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=
 416
 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
 420
 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
 424
 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
 454
 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
 493}
 494#
 495# List all submodules, prefixed with:
 496#  - submodule not initialized
 497#  + different revision checked out
 498#
 499# If --cached was specified the revision in the index will be printed
 500# instead of the currently checked out revision.
 501#
 502# $@ = requested paths (default to all)
 503#
 504cmd_status()
 505{
 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
 529
 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
 553}
 554
 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.
 560
 561while test $# != 0 && test -z "$command"
 562do
 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
 592done
 593
 594# No command word defaults to "status"
 595test -n "$command" || command=status
 596
 597# "-b branch" is accepted only by "add"
 598if test -n "$branch" && test "$command" != add
 599then
 600        usage
 601fi
 602
 603# "--cached" is accepted only by "status" and "summary"
 604if test -n "$cached" && test "$command" != status -a "$command" != summary
 605then
 606        usage
 607fi
 608
 609"cmd_$command" "$@"