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