Code

Merge branch 'db/clone-in-c'
authorJunio C Hamano <gitster@pobox.com>
Sun, 25 May 2008 20:38:44 +0000 (13:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 25 May 2008 20:41:37 +0000 (13:41 -0700)
* db/clone-in-c:
  Add test for cloning with "--reference" repo being a subset of source repo
  Add a test for another combination of --reference
  Test that --reference actually suppresses fetching referenced objects
  clone: fall back to copying if hardlinking fails
  builtin-clone.c: Need to closedir() in copy_or_link_directory()
  builtin-clone: fix initial checkout
  Build in clone
  Provide API access to init_db()
  Add a function to set a non-default work tree
  Allow for having for_each_ref() list extra refs
  Have a constant extern refspec for "--tags"
  Add a library function to add an alternate to the alternates file
  Add a lockfile function to append to a file
  Mark the list of refs to fetch as const

Conflicts:

cache.h
t/t5700-clone-reference.sh

13 files changed:
1  2 
Makefile
builtin-fetch.c
builtin-init-db.c
cache.h
contrib/examples/git-clone.sh
environment.c
lockfile.c
refs.c
remote.c
remote.h
sha1_file.c
t/t5700-clone-reference.sh
transport.c

diff --cc Makefile
Simple merge
diff --cc builtin-fetch.c
Simple merge
index b061317275792ccb33eea80a5071b72ed6f8d6a7,5650685e4ecb0f7a9352f95abe6bd7ef3d4c82d6..3968c9911ff23717a4505ba9a02af6e27bf0b4a0
@@@ -251,12 -248,14 +248,14 @@@ static int create_default_files(const c
                /* allow template config file to override the default */
                if (log_all_ref_updates == -1)
                    git_config_set("core.logallrefupdates", "true");
-               if (work_tree != git_work_tree_cfg)
+               if (prefixcmp(git_dir, work_tree) ||
+                   strcmp(git_dir + strlen(work_tree), "/.git")) {
                        git_config_set("core.worktree", work_tree);
+               }
        }
  
 -      /* Check if symlink is supported in the work tree */
        if (!reinit) {
 +              /* Check if symlink is supported in the work tree */
                path[len] = 0;
                strcpy(path + len, "tXXXXXX");
                if (!close(xmkstemp(path)) &&
diff --cc cache.h
index ef330b4c6b71ebf5775b9fbb889ea635c7422c30,f3ad9741cc3e57104b2cdc38de16012f13b241d1..3d4e8e77d8d47f1861af6488aacb1e8a9a57ac8f
+++ b/cache.h
@@@ -316,7 -311,7 +316,8 @@@ extern char *get_index_file(void)
  extern char *get_graft_file(void);
  extern int set_git_dir(const char *path);
  extern const char *get_git_work_tree(void);
 +extern const char *read_gitfile_gently(const char *path);
+ extern void set_git_work_tree(const char *tree);
  
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
index 0000000000000000000000000000000000000000,8c7fc7f6317113fcef923dcc625fb94e13fb77a0..547228e13ce60e575d0b4a10a322edfff6c0622c
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,523 +1,525 @@@
 -      err=$?
+ #!/bin/sh
+ #
+ # Copyright (c) 2005, Linus Torvalds
+ # Copyright (c) 2005, Junio C Hamano
+ #
+ # Clone a repository into a different directory that does not yet exist.
+ # See git-sh-setup why.
+ unset CDPATH
+ OPTIONS_SPEC="\
+ git-clone [options] [--] <repo> [<dir>]
+ --
+ n,no-checkout        don't create a checkout
+ bare                 create a bare repository
+ naked                create a bare repository
+ l,local              to clone from a local repository
+ no-hardlinks         don't use local hardlinks, always copy
+ s,shared             setup as a shared repository
+ template=            path to the template directory
+ q,quiet              be quiet
+ reference=           reference repository
+ o,origin=            use <name> instead of 'origin' to track upstream
+ u,upload-pack=       path to git-upload-pack on the remote
+ depth=               create a shallow clone of that depth
+ use-separate-remote  compatibility, do not use
+ no-separate-remote   compatibility, do not use"
+ die() {
+       echo >&2 "$@"
+       exit 1
+ }
+ usage() {
+       exec "$0" -h
+ }
+ eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+ get_repo_base() {
+       (
+               cd "`/bin/pwd`" &&
+               cd "$1" || cd "$1.git" &&
+               {
+                       cd .git
+                       pwd
+               }
+       ) 2>/dev/null
+ }
+ if [ -n "$GIT_SSL_NO_VERIFY" -o \
+       "`git config --bool http.sslVerify`" = false ]; then
+     curl_extra_args="-k"
+ fi
+ http_fetch () {
+       # $1 = Remote, $2 = Local
+       curl -nsfL $curl_extra_args "$1" >"$2"
+       curl_exit_status=$?
+       case $curl_exit_status in
+       126|127) exit ;;
+       *)       return $curl_exit_status ;;
+       esac
+ }
+ clone_dumb_http () {
+       # $1 - remote, $2 - local
+       cd "$2" &&
+       clone_tmp="$GIT_DIR/clone-tmp" &&
+       mkdir -p "$clone_tmp" || exit 1
+       if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+               "`git config --bool http.noEPSV`" = true ]; then
+               curl_extra_args="${curl_extra_args} --disable-epsv"
+       fi
+       http_fetch "$1/info/refs" "$clone_tmp/refs" ||
+               die "Cannot get remote repository information.
+ Perhaps git-update-server-info needs to be run there?"
+       test "z$quiet" = z && v=-v || v=
+       while read sha1 refname
+       do
+               name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
+               case "$name" in
+               *^*)    continue;;
+               esac
+               case "$bare,$name" in
+               yes,* | ,heads/* | ,tags/*) ;;
+               *)      continue ;;
+               esac
+               if test -n "$use_separate_remote" &&
+                  branch_name=`expr "z$name" : 'zheads/\(.*\)'`
+               then
+                       tname="remotes/$origin/$branch_name"
+               else
+                       tname=$name
+               fi
+               git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
+       done <"$clone_tmp/refs"
+       rm -fr "$clone_tmp"
+       http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
+       rm -f "$GIT_DIR/REMOTE_HEAD"
+       if test -f "$GIT_DIR/REMOTE_HEAD"; then
+               head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+               case "$head_sha1" in
+               'ref: refs/'*)
+                       ;;
+               *)
+                       git-http-fetch $v -a "$head_sha1" "$1" ||
+                       rm -f "$GIT_DIR/REMOTE_HEAD"
+                       ;;
+               esac
+       fi
+ }
+ quiet=
+ local=no
+ use_local_hardlink=yes
+ local_shared=no
+ unset template
+ no_checkout=
+ upload_pack=
+ bare=
+ reference=
+ origin=
+ origin_override=
+ use_separate_remote=t
+ depth=
+ no_progress=
+ local_explicitly_asked_for=
+ test -t 1 || no_progress=--no-progress
+ while test $# != 0
+ do
+       case "$1" in
+       -n|--no-checkout)
+               no_checkout=yes ;;
+       --naked|--bare)
+               bare=yes ;;
+       -l|--local)
+               local_explicitly_asked_for=yes
+               use_local_hardlink=yes
+               ;;
+       --no-hardlinks)
+               use_local_hardlink=no ;;
+       -s|--shared)
+               local_shared=yes ;;
+       --template)
+               shift; template="--template=$1" ;;
+       -q|--quiet)
+               quiet=-q ;;
+       --use-separate-remote|--no-separate-remote)
+               die "clones are always made with separate-remote layout" ;;
+       --reference)
+               shift; reference="$1" ;;
+       -o|--origin)
+               shift;
+               case "$1" in
+               '')
+                   usage ;;
+               */*)
+                   die "'$1' is not suitable for an origin name"
+               esac
+               git check-ref-format "heads/$1" ||
+                   die "'$1' is not suitable for a branch name"
+               test -z "$origin_override" ||
+                   die "Do not give more than one --origin options."
+               origin_override=yes
+               origin="$1"
+               ;;
+       -u|--upload-pack)
+               shift
+               upload_pack="--upload-pack=$1" ;;
+       --depth)
+               shift
+               depth="--depth=$1" ;;
+       --)
+               shift
+               break ;;
+       *)
+               usage ;;
+       esac
+       shift
+ done
+ repo="$1"
+ test -n "$repo" ||
+     die 'you must specify a repository to clone.'
+ # --bare implies --no-checkout and --no-separate-remote
+ if test yes = "$bare"
+ then
+       if test yes = "$origin_override"
+       then
+               die '--bare and --origin $origin options are incompatible.'
+       fi
+       no_checkout=yes
+       use_separate_remote=
+ fi
+ if test -z "$origin"
+ then
+       origin=origin
+ fi
+ # Turn the source into an absolute path if
+ # it is local
+ if base=$(get_repo_base "$repo"); then
+       repo="$base"
+       if test -z "$depth"
+       then
+               local=yes
+       fi
+ elif test -f "$repo"
+ then
+       case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
+ fi
+ # Decide the directory name of the new repository
+ if test -n "$2"
+ then
+       dir="$2"
+       test $# = 2 || die "excess parameter to git-clone"
+ else
+       # Derive one from the repository name
+       # Try using "humanish" part of source repo if user didn't specify one
+       if test -f "$repo"
+       then
+               # Cloning from a bundle
+               dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
+       else
+               dir=$(echo "$repo" |
+                       sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+       fi
+ fi
+ [ -e "$dir" ] && die "destination directory '$dir' already exists."
+ [ yes = "$bare" ] && unset GIT_WORK_TREE
+ [ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
+ die "working tree '$GIT_WORK_TREE' already exists."
+ D=
+ W=
+ cleanup() {
 -trap cleanup 0
+       test -z "$D" && rm -rf "$dir"
+       test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
+       cd ..
+       test -n "$D" && rm -rf "$D"
+       test -n "$W" && rm -rf "$W"
+       exit $err
+ }
 -              find objects -depth -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \
++trap 'err=$?; cleanup' 0
+ mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
+ test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
+ W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
+ if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
+       GIT_DIR="$D"
+ else
+       GIT_DIR="$D/.git"
+ fi &&
+ export GIT_DIR &&
+ GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage
+ if test -n "$bare"
+ then
+       GIT_CONFIG="$GIT_DIR/config" git config core.bare true
+ fi
+ if test -n "$reference"
+ then
+       ref_git=
+       if test -d "$reference"
+       then
+               if test -d "$reference/.git/objects"
+               then
+                       ref_git="$reference/.git"
+               elif test -d "$reference/objects"
+               then
+                       ref_git="$reference"
+               fi
+       fi
+       if test -n "$ref_git"
+       then
+               ref_git=$(cd "$ref_git" && pwd)
+               echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
+               (
+                       GIT_DIR="$ref_git" git for-each-ref \
+                               --format='%(objectname) %(*objectname)'
+               ) |
+               while read a b
+               do
+                       test -z "$a" ||
+                       git update-ref "refs/reference-tmp/$a" "$a"
+                       test -z "$b" ||
+                       git update-ref "refs/reference-tmp/$b" "$b"
+               done
+       else
+               die "reference repository '$reference' is not a local directory."
+       fi
+ fi
+ rm -f "$GIT_DIR/CLONE_HEAD"
+ # We do local magic only when the user tells us to.
+ case "$local" in
+ yes)
+       ( cd "$repo/objects" ) ||
+               die "cannot chdir to local '$repo/objects'."
+       if test "$local_shared" = yes
+       then
+               mkdir -p "$GIT_DIR/objects/info"
+               echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
+       else
+               cpio_quiet_flag=""
+               cpio --help 2>&1 | grep -- --quiet >/dev/null && \
+                       cpio_quiet_flag=--quiet
+               l= &&
+               if test "$use_local_hardlink" = yes
+               then
+                       # See if we can hardlink and drop "l" if not.
+                       sample_file=$(cd "$repo" && \
+                                     find objects -type f -print | sed -e 1q)
+                       # objects directory should not be empty because
+                       # we are cloning!
+                       test -f "$repo/$sample_file" ||
+                               die "fatal: cannot clone empty repository"
+                       if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
+                       then
+                               rm -f "$GIT_DIR/objects/sample"
+                               l=l
+                       elif test -n "$local_explicitly_asked_for"
+                       then
+                               echo >&2 "Warning: -l asked but cannot hardlink to $repo"
+                       fi
+               fi &&
+               cd "$repo" &&
++              # Create dirs using umask and permissions and destination
++              find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) &&
++              # Copy existing 0444 permissions on content
++              find objects ! -type d -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \
+                       exit 1
+       fi
+       git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
+       ;;
+ *)
+       case "$repo" in
+       rsync://*)
+               case "$depth" in
+               "") ;;
+               *) die "shallow over rsync not supported" ;;
+               esac
+               rsync $quiet -av --ignore-existing  \
+                       --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
+               exit
+               # Look at objects/info/alternates for rsync -- http will
+               # support it natively and git native ones will do it on the
+               # remote end.  Not having that file is not a crime.
+               rsync -q "$repo/objects/info/alternates" \
+                       "$GIT_DIR/TMP_ALT" 2>/dev/null ||
+                       rm -f "$GIT_DIR/TMP_ALT"
+               if test -f "$GIT_DIR/TMP_ALT"
+               then
+                   ( cd "$D" &&
+                     . git-parse-remote &&
+                     resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
+                   while read alt
+                   do
+                       case "$alt" in 'bad alternate: '*) die "$alt";; esac
+                       case "$quiet" in
+                       '')     echo >&2 "Getting alternate: $alt" ;;
+                       esac
+                       rsync $quiet -av --ignore-existing  \
+                           --exclude info "$alt" "$GIT_DIR/objects" || exit
+                   done
+                   rm -f "$GIT_DIR/TMP_ALT"
+               fi
+               git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
+               ;;
+       https://*|http://*|ftp://*)
+               case "$depth" in
+               "") ;;
+               *) die "shallow over http or ftp not supported" ;;
+               esac
+               if test -z "@@NO_CURL@@"
+               then
+                       clone_dumb_http "$repo" "$D"
+               else
+                       die "http transport not supported, rebuild Git with curl support"
+               fi
+               ;;
+       *)
+               if [ -f "$repo" ] ; then
+                       git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
+                       die "unbundle from '$repo' failed."
+               else
+                       case "$upload_pack" in
+                       '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
+                       *) git-fetch-pack --all -k \
+                               $quiet "$upload_pack" $depth $no_progress "$repo" ;;
+                       esac >"$GIT_DIR/CLONE_HEAD" ||
+                       die "fetch-pack from '$repo' failed."
+               fi
+               ;;
+       esac
+       ;;
+ esac
+ test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
+ if test -f "$GIT_DIR/CLONE_HEAD"
+ then
+       # Read git-fetch-pack -k output and store the remote branches.
+       if [ -n "$use_separate_remote" ]
+       then
+               branch_top="remotes/$origin"
+       else
+               branch_top="heads"
+       fi
+       tag_top="tags"
+       while read sha1 name
+       do
+               case "$name" in
+               *'^{}')
+                       continue ;;
+               HEAD)
+                       destname="REMOTE_HEAD" ;;
+               refs/heads/*)
+                       destname="refs/$branch_top/${name#refs/heads/}" ;;
+               refs/tags/*)
+                       destname="refs/$tag_top/${name#refs/tags/}" ;;
+               *)
+                       continue ;;
+               esac
+               git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
+       done < "$GIT_DIR/CLONE_HEAD"
+ fi
+ if test -n "$W"; then
+       cd "$W" || exit
+ else
+       cd "$D" || exit
+ fi
+ if test -z "$bare"
+ then
+       # a non-bare repository is always in separate-remote layout
+       remote_top="refs/remotes/$origin"
+       head_sha1=
+       test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+       case "$head_sha1" in
+       'ref: refs/'*)
+               # Uh-oh, the remote told us (http transport done against
+               # new style repository with a symref HEAD).
+               # Ideally we should skip the guesswork but for now
+               # opt for minimum change.
+               head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
+               head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
+               ;;
+       esac
+       # The name under $remote_top the remote HEAD seems to point at.
+       head_points_at=$(
+               (
+                       test -f "$GIT_DIR/$remote_top/master" && echo "master"
+                       cd "$GIT_DIR/$remote_top" &&
+                       find . -type f -print | sed -e 's/^\.\///'
+               ) | (
+               done=f
+               while read name
+               do
+                       test t = $done && continue
+                       branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
+                       if test "$head_sha1" = "$branch_tip"
+                       then
+                               echo "$name"
+                               done=t
+                       fi
+               done
+               )
+       )
+       # Upstream URL
+       git config remote."$origin".url "$repo" &&
+       # Set up the mappings to track the remote branches.
+       git config remote."$origin".fetch \
+               "+refs/heads/*:$remote_top/*" '^$' &&
+       # Write out remote.$origin config, and update our "$head_points_at".
+       case "$head_points_at" in
+       ?*)
+               # Local default branch
+               git symbolic-ref HEAD "refs/heads/$head_points_at" &&
+               # Tracking branch for the primary branch at the remote.
+               git update-ref HEAD "$head_sha1" &&
+               rm -f "refs/remotes/$origin/HEAD"
+               git symbolic-ref "refs/remotes/$origin/HEAD" \
+                       "refs/remotes/$origin/$head_points_at" &&
+               git config branch."$head_points_at".remote "$origin" &&
+               git config branch."$head_points_at".merge "refs/heads/$head_points_at"
+               ;;
+       '')
+               if test -z "$head_sha1"
+               then
+                       # Source had nonexistent ref in HEAD
+                       echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
+                       no_checkout=t
+               else
+                       # Source had detached HEAD pointing nowhere
+                       git update-ref --no-deref HEAD "$head_sha1" &&
+                       rm -f "refs/remotes/$origin/HEAD"
+               fi
+               ;;
+       esac
+       case "$no_checkout" in
+       '')
+               test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
+               git read-tree -m -u $v HEAD HEAD
+       esac
+ fi
+ rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
+ trap - 0
diff --cc environment.c
Simple merge
diff --cc lockfile.c
Simple merge
diff --cc refs.c
Simple merge
diff --cc remote.c
index 9e4f2b84d90cb97a6cb19779325f9ea443a76e43,9cb40afd0e3a4a35d9811ba48c5b08a0b39cf565..75a12c0762160dd877d0b6f63f8ec47228b7fee4
+++ b/remote.c
@@@ -2,6 -2,15 +2,16 @@@
  #include "remote.h"
  #include "refs.h"
  
+ static struct refspec s_tag_refspec = {
+       0,
+       1,
++      0,
+       "refs/tags/",
+       "refs/tags/"
+ };
+ const struct refspec *tag_refspec = &s_tag_refspec;
  struct counted_string {
        size_t len;
        const char *s;
diff --cc remote.h
index c2f557357fd4a0eb247f0e6f688efd10f946a08a,f0a79de210ce34c62781ebb7e0aa8a66390226b7..8eed87ba5ab78eb4635632c21843590467d0d864
+++ b/remote.h
@@@ -53,10 -51,10 +53,12 @@@ struct refspec 
        char *dst;
  };
  
+ extern const struct refspec *tag_refspec;
  struct ref *alloc_ref(unsigned namelen);
  
 +struct ref *alloc_ref_from_str(const char* str);
 +
  struct ref *copy_ref_list(const struct ref *ref);
  
  int check_ref_type(const struct ref *ref, int flags);
diff --cc sha1_file.c
Simple merge
index e5619a9f5c9aae8c9565dd6937c20ce6401d26d4,0112c218e0ebcd67fede154f59a82a1a5b6a4aec..e1ca7303ac83a79eb4668c8f828e068c1220e72d
@@@ -50,8 -52,13 +52,13 @@@ diff expected current
  
  cd "$base_dir"
  
+ rm -f $U
  test_expect_success 'cloning with reference (no -l -s)' \
- 'git clone --reference B "file://$(pwd)/A" D'
 -'GIT_DEBUG_SEND_PACK=3 git clone --reference B file://`pwd`/A D 3>$U'
++'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>$U'
+ test_expect_success 'fetched no objects' \
+ '! grep "^want" $U'
  
  cd "$base_dir"
  
diff --cc transport.c
Simple merge