Do a “git export” (like “svn export”)?

后端 未结 30 2869
暖寄归人
暖寄归人 2020-11-21 22:33

I\'ve been wondering whether there is a good \"git export\" solution that creates a copy of a tree without the .git repository directory. There are at least thr

相关标签:
30条回答
  • 2020-11-21 22:40

    I think @Aredridel's post was closest, but there's a bit more to that - so I will add this here; the thing is, in svn, if you're in a subfolder of a repo, and you do:

    /media/disk/repo_svn/subdir$ svn export . /media/disk2/repo_svn_B/subdir
    

    then svn will export all files that are under revision control (they could have also freshly Added; or Modified status) - and if you have other "junk" in that directory (and I'm not counting .svn subfolders here, but visible stuff like .o files), it will not be exported; only those files registered by the SVN repo will be exported. For me, one nice thing is that this export also includes files with local changes that have not been committed yet; and another nice thing is that the timestamps of the exported files are the same as the original ones. Or, as svn help export puts it:

    1. Exports a clean directory tree from the working copy specified by PATH1, at revision REV if it is given, otherwise at WORKING, into PATH2. ... If REV is not specified, all local changes will be preserved. Files not under version control will not be copied.

    To realize that git will not preserve the timestamps, compare the output of these commands (in a subfolder of a git repo of your choice):

    /media/disk/git_svn/subdir$ ls -la .
    

    ... and:

    /media/disk/git_svn/subdir$ git archive --format=tar --prefix=junk/ HEAD | (tar -t -v --full-time -f -)
    

    ... and I, in any case, notice that git archive causes all the timestamps of the archived file to be the same! git help archive says:

    git archive behaves differently when given a tree ID versus when given a commit ID or tag ID. In the first case the current time is used as the modification time of each file in the archive. In the latter case the commit time as recorded in the referenced commit object is used instead.

    ... but apparently both cases set the "modification time of each file"; thereby not preserving the actual timestamps of those files!

    So, in order to also preserve the timestamps, here is a bash script, which is actually a "one-liner", albeit somewhat complicated - so below it is posted in multiple lines:

    /media/disk/git_svn/subdir$ git archive --format=tar master | (tar tf -) | (\
      DEST="/media/diskC/tmp/subdirB"; \
      CWD="$PWD"; \
      while read line; do \
        DN=$(dirname "$line"); BN=$(basename "$line"); \
        SRD="$CWD"; TGD="$DEST"; \
        if [ "$DN" != "." ]; then \
          SRD="$SRD/$DN" ; TGD="$TGD/$DN" ; \
          if [ ! -d "$TGD" ] ; then \
            CMD="mkdir \"$TGD\"; touch -r \"$SRD\" \"$TGD\""; \
            echo "$CMD"; \
            eval "$CMD"; \
          fi; \
        fi; \
        CMD="cp -a \"$SRD/$BN\" \"$TGD/\""; \
        echo "$CMD"; \
        eval "$CMD"; \
        done \
    )
    

    Note that it is assumed that you're exporting the contents in "current" directory (above, /media/disk/git_svn/subdir) - and the destination you're exporting into is somewhat inconveniently placed, but it is in DEST environment variable. Note that with this script; you must create the DEST directory manually yourself, before running the above script.

    After the script is ran, you should be able to compare:

    ls -la /media/disk/git_svn/subdir
    ls -la /media/diskC/tmp/subdirB   # DEST
    

    ... and hopefully see the same timestamps (for those files that were under version control).

    Hope this helps someone,
    Cheers!

    0 讨论(0)
  • 2020-11-21 22:41

    enter image description here

    A special case answer if the repository is hosted on GitHub.

    Just use svn export.

    As far as I know Github does not allow archive --remote. Although GitHub is svn compatible and they do have all git repos svn accessible so you could just use svn export like you normally would with a few adjustments to your GitHub url.

    For example to export an entire repository, notice how trunk in the URL replaces master (or whatever the project's HEAD branch is set to):

    svn export https://github.com/username/repo-name/trunk/
    

    And you can export a single file or even a certain path or folder:

    svn export https://github.com/username/repo-name/trunk/src/lib/folder
    

    Example with jQuery JavaScript Library

    The HEAD branch or master branch will be available using trunk:

    svn ls https://github.com/jquery/jquery/trunk
    

    The non-HEAD branches will be accessible under /branches/:

    svn ls https://github.com/jquery/jquery/branches/2.1-stable
    

    All tags under /tags/ in the same fashion:

    svn ls https://github.com/jquery/jquery/tags/2.1.3
    
    0 讨论(0)
  • 2020-11-21 22:41

    For GitHub users, the git archive --remote method won't work directly, as the export URL is ephemeral. You must ask GitHub for the URL, then download that URL. curl makes that easy:

    curl -L https://api.github.com/repos/VENDOR/PROJECT/tarball | tar xzf -
    

    This will give you the exported code in a local directory. Example:

    $ curl -L https://api.github.com/repos/jpic/bashworks/tarball | tar xzf -
    $ ls jpic-bashworks-34f4441/
    break  conf  docs  hack  LICENSE  mlog  module  mpd  mtests  os  README.rst  remote  todo  vcs  vps  wepcrack
    

    Edit
    If you want the code put into a specific, existing directory (rather than the random one from github):

    curl -L https://api.github.com/repos/VENDOR/PROJECT/tarball | \
    tar xzC /path/you/want --strip 1
    
    0 讨论(0)
  • 2020-11-21 22:42

    You can archive a remote repo at any commit as zip file.

    git archive --format=zip --output=archive.zip --remote=USERNAME@HOSTNAME:PROJECTNAME.git HASHOFGITCOMMIT
    
    0 讨论(0)
  • 2020-11-21 22:42

    Bash-implementation of git-export.

    I have segmented the .empty file creation and removal processes on their own function, with the purpose of re-using them in the 'git-archive' implementation (will be posted later on).

    I have also added the '.gitattributes' file to the process in order to remove un-wanted files from the target export folder. Included verbosity to the process while making the 'git-export' function more efficient.

    EMPTY_FILE=".empty";

    function create_empty () {
    ## Processing path (target-dir):
        TRG_PATH="${1}";
    ## Component(s):
        EXCLUDE_DIR=".git";
    echo -en "\nAdding '${EMPTY_FILE}' files to empty folder(s): ...";
        find ${TRG_PATH} -not -path "*/${EXCLUDE_DIR}/*" -type d -empty -exec touch {}/${EMPTY_FILE} \;
    #echo "done.";
    ## Purging SRC/TRG_DIRs variable(s):
        unset TRG_PATH EMPTY_FILE EXCLUDE_DIR;
        return 0;
      }
    
    declare -a GIT_EXCLUDE;
    function load_exclude () {
        SRC_PATH="${1}";
        ITEMS=0; while read LINE; do
    #      echo -e "Line [${ITEMS}]: '${LINE%%\ *}'";
          GIT_EXCLUDE[((ITEMS++))]=${LINE%%\ *};
        done < ${SRC_PATH}/.gitattributes;
        GIT_EXCLUDE[${ITEMS}]="${EMPTY_FILE}";
    ## Purging variable(s):
        unset SRC_PATH ITEMS;
        return 0;
      }
    
    function purge_empty () {
    ## Processing path (Source/Target-dir):
        SRC_PATH="${1}";
        TRG_PATH="${2}";
    echo -e "\nPurging Git-Specific component(s): ... ";
        find ${SRC_PATH} -type f -name ${EMPTY_FILE} -exec /bin/rm '{}' \;
        for xRULE in ${GIT_EXCLUDE[@]}; do
    echo -en "    '${TRG_PATH}/{${xRULE}}' files ... ";
          find ${TRG_PATH} -type f -name "${xRULE}" -exec /bin/rm -rf '{}' \;
    echo "done.'";
        done;
    echo -e "done.\n"
    ## Purging SRC/TRG_PATHs variable(s):
        unset SRC_PATH; unset TRG_PATH;
        return 0;
      }
    
    function git-export () {
        TRG_DIR="${1}"; SRC_DIR="${2}";
        if [ -z "${SRC_DIR}" ]; then SRC_DIR="${PWD}"; fi
        load_exclude "${SRC_DIR}";
    ## Dynamically added '.empty' files to the Git-Structure:
        create_empty "${SRC_DIR}";
        GIT_COMMIT="Including '${EMPTY_FILE}' files into Git-Index container."; #echo -e "\n${GIT_COMMIT}";
        git add .; git commit --quiet --all --verbose --message "${GIT_COMMIT}";
        if [ "${?}" -eq 0 ]; then echo " done."; fi
        /bin/rm -rf ${TRG_DIR} && mkdir -p "${TRG_DIR}";
    echo -en "\nChecking-Out Index component(s): ... ";
        git checkout-index --prefix=${TRG_DIR}/ -q -f -a
    ## Reset: --mixed = reset HEAD and index:
        if [ "${?}" -eq 0 ]; then
    echo "done."; echo -en "Resetting HEAD and Index: ... ";
            git reset --soft HEAD^;
            if [ "${?}" -eq 0 ]; then
    echo "done.";
    ## Purging Git-specific components and '.empty' files from Target-Dir:
                purge_empty "${SRC_DIR}" "${TRG_DIR}"
              else echo "failed.";
            fi
    ## Archiving exported-content:
    echo -en "Archiving Checked-Out component(s): ... ";
            if [ -f "${TRG_DIR}.tgz" ]; then /bin/rm ${TRG_DIR}.tgz; fi
            cd ${TRG_DIR} && tar -czf ${TRG_DIR}.tgz ./; cd ${SRC_DIR}
    echo "done.";
    ## Listing *.tgz file attributes:
    ## Warning: Un-TAR this file to a specific directory:
            ls -al ${TRG_DIR}.tgz
          else echo "failed.";
        fi
    ## Purgin all references to Un-Staged File(s):
       git reset HEAD;
    ## Purging SRC/TRG_DIRs variable(s):
        unset SRC_DIR; unset TRG_DIR;
        echo "";
        return 0;
      }
    

    Output:

    $ git-export /tmp/rel-1.0.0

    Adding '.empty' files to empty folder(s): ... done.

    Checking-Out Index component(s): ... done.

    Resetting HEAD and Index: ... done.

    Purging Git-Specific component(s): ...

    '/tmp/rel-1.0.0/{.buildpath}' files ... done.'

    '/tmp/rel-1.0.0/{.project}' files ... done.'

    '/tmp/rel-1.0.0/{.gitignore}' files ... done.'

    '/tmp/rel-1.0.0/{.git}' files ... done.'

    '/tmp/rel-1.0.0/{.gitattributes}' files ... done.'

    '/tmp/rel-1.0.0/{*.mno}' files ... done.'

    '/tmp/rel-1.0.0/{*~}' files ... done.'

    '/tmp/rel-1.0.0/{.*~}' files ... done.'

    '/tmp/rel-1.0.0/{*.swp}' files ... done.'

    '/tmp/rel-1.0.0/{*.swo}' files ... done.'

    '/tmp/rel-1.0.0/{.DS_Store}' files ... done.'

    '/tmp/rel-1.0.0/{.settings}' files ... done.'

    '/tmp/rel-1.0.0/{.empty}' files ... done.'

    done.

    Archiving Checked-Out component(s): ... done.

    -rw-r--r-- 1 admin wheel 25445901 3 Nov 12:57 /tmp/rel-1.0.0.tgz

    I have now incorporated the 'git archive' functionality into a single process that makes use of 'create_empty' function and other features.

    function git-archive () {
        PREFIX="${1}"; ## sudo mkdir -p ${PREFIX}
        REPO_PATH="`echo "${2}"|awk -F: '{print $1}'`";
        RELEASE="`echo "${2}"|awk -F: '{print $2}'`";
        USER_PATH="${PWD}";
    echo "$PREFIX $REPO_PATH $RELEASE $USER_PATH";
    ## Dynamically added '.empty' files to the Git-Structure:
        cd "${REPO_PATH}"; populate_empty .; echo -en "\n";
    #    git archive --prefix=git-1.4.0/ -o git-1.4.0.tar.gz v1.4.0
    # e.g.: git-archive /var/www/htdocs /repos/domain.name/website:rel-1.0.0 --explode
        OUTPUT_FILE="${USER_PATH}/${RELEASE}.tar.gz";
        git archive --verbose --prefix=${PREFIX}/ -o ${OUTPUT_FILE} ${RELEASE}
        cd "${USER_PATH}";
        if [[ "${3}" =~ [--explode] ]]; then
          if [ -d "./${RELEASE}" ]; then /bin/rm -rf "./${RELEASE}"; fi
          mkdir -p ./${RELEASE}; tar -xzf "${OUTPUT_FILE}" -C ./${RELEASE}
        fi
    ## Purging SRC/TRG_DIRs variable(s):
        unset PREFIX REPO_PATH RELEASE USER_PATH OUTPUT_FILE;
        return 0;
      }
    
    0 讨论(0)
  • 2020-11-21 22:42

    Doing it the easy way, this is a function for .bash_profile, it directly unzips the archive on current location, configure first your usual [url:path]. NOTE: With this function you avoid the clone operation, it gets directly from the remote repo.

    gitss() {
        URL=[url:path]
    
        TMPFILE="`/bin/tempfile`"
        if [ "$1" = "" ]; then
            echo -e "Use: gitss repo [tree/commit]\n"
            return
        fi
        if [ "$2" = "" ]; then
            TREEISH="HEAD"
        else
            TREEISH="$2"
        fi
        echo "Getting $1/$TREEISH..."
        git archive --format=zip --remote=$URL/$1 $TREEISH > $TMPFILE && unzip $TMPFILE && echo -e "\nDone\n"
        rm $TMPFILE
    }
    

    Alias for .gitconfig, same configuration required (TAKE CARE executing the command inside .git projects, it ALWAYS jumps to the base dir previously as said here, until this is fixed I personally prefer the function

    ss = !env GIT_TMPFILE="`/bin/tempfile`" sh -c 'git archive --format=zip --remote=[url:path]/$1 $2 \ > $GIT_TMPFILE && unzip $GIT_TMPFILE && rm $GIT_TMPFILE' -
    
    0 讨论(0)
提交回复
热议问题