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