使用Git管理两个版本的网站

时间:2012-04-07 21:53:58

标签: git

我确信这属于使用Git For It It It It Not Itove 类别,但我想分享我正在做的事情,以便专家可以评论它。

我在我的实时服务器上使用虚拟主机,这样我就可以同时运行2个版本的网站。一个是example.com,另一个是staging.example.com。暂存站点用于测试新功能,我创建了一种方法将站点的git repo的两个分支(例如, staging master )链接到各自的站点根目录。

首先,我在远程服务器上设置了git,以便在我推送(using this great technique)时自动将最新的主服务器签出到Web根目录。

然后,在我的post-receive hook中,我把它放在:

#!/bin/sh
GIT_WORK_TREE=/var/www git checkout master -f
GIT_WORK_TREE=/path/to/staging/site/webroot git checkout staging -f

使用这种方法,我可以保留两个版本的站点,在git中使用两个分支),当我推送时,登台站点会随着登台分支中的任何新更改而更新,并且与主站点相同。

我发现在将这些功能公开之前管理演示新功能是一种很好的方式。

我不应该这样做吗?有没有更好的办法?其他想法或顾虑?

谢谢, 杰森

2 个答案:

答案 0 :(得分:4)

在我看来,你正在以一种Git应该用的方式使用Git。您有两个版本的相同文件,并且您希望将每个“版本”分别维护为分支。

我假设当您准备将更改从分段合并到母版时,您只需在git merge staging分支内执行类似git rebase staging / master的操作,然后从那里开始。

你根本没有使用Git。

答案 1 :(得分:3)

你想知道如何成为发烧友。我将通过收发后的电子邮件钩子脚本来展示它是如何工作的(部分)。这变得很长! : - )

好的,所以,这里是post-receieve-email的关键位(重新格式化了一点):

while read oldrev newrev refname; do
    prep_for_email $oldrev $newrev $refname || continue
    generate_email $maxlines | send_mail
done

这会从输入流中读取旧的SHA,新的SHA和引用名称(我认为这是git的失败之一:你只能读取一次这个流,然后它就消失了;这使得连接一堆其他无关的钩子过于困难)并调用两个shell函数来检查它们,然后忽略或对每个函数执行一些操作。

现在来自prep_for_email shell函数的关键位,带有注释:

prep_for_email()
{
    oldrev=$(git rev-parse $1)
    newrev=$(git rev-parse $2)
    refname="$3"

这里的rev-parse(以及我上面提到的允许命令行参数的一些代码)允许你提供诸如" HEAD"之类的东西rev-names。和" HEAD ^"。实际的post-receive挂钩总是获取原始SHA1,因此rev-parse调用是no-ops。

    # --- Interpret
    # 0000->1234 (create)
    # 1234->2345 (update)
    # 2345->0000 (delete)
    if expr "$oldrev" : '0*$' >/dev/null
    then
        change_type="create"
    else
        if expr "$newrev" : '0*$' >/dev/null
        then
            change_type="delete"
        else
            change_type="update"
        fi
    fi

在收件后挂钩中,如果" old" SHA1是0000000000000000000000000000000000000000(全为零,为40 0s;上面的expr只检查全零),这意味着" ref"之前的论证不存在,现在也存在。这通常是分支创建操作,但也可以是标记创建。另一方面,如果"新" SHA1全为零," ref"参数确实之前存在,现在并不存在:通常是分支删除操作。其他任何东西,ref-name 使用解析为旧版本,现在解析为new-rev。这通常是分支更新,但也可以是标签移动。

接下来,电子邮件钩子有一些好的"偏执狂风格"编程,根据git repo中的实际底层对象类型交叉检查引用名称:

    # --- Get the revision types
    newrev_type=$(git cat-file -t $newrev 2> /dev/null)
    oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
    case "$change_type" in
    create|update)
        rev="$newrev"
        rev_type="$newrev_type"
        ;;
    delete)
        rev="$oldrev"
        rev_type="$oldrev_type"
        ;;
    esac

如果引用名称的格式为refs/tags/*且更新是针对带注释的标记,则应将$oldrev_type$newrev_type都设置为tag。 (如果它是一个轻量级标记,则它们都将是commit。如果轻量级标记变为带注释的标记,则旧类型将提交,新类型将为标记,依此类推。)和当然,如果你要删除分支或标签,那么" new" rev将全部为0,$newrev_type将为空字符串,因为git cat-file -t将失败(这就是2> /dev/null的原因。)

(旁白:没有任何理由引用git cat-file -t引用其参数,而且没有引用。可能只是有人在遇到通常的空字符串参数后对引用过于满意问题,但错过了一个。幸运的是,在这种情况下它无论如何都是无害的。:-))

电子邮件脚本的语句很长case

case "$refname","$rev_type" in
    ...
esac

确保refs/heads/*上的操作始终为commit。如果没有,它会向stderr打印一条消息(在git push上,该消息将被发送给执行推送的人,前缀为remote:,以便某人可以看到它):

    refs/heads/*,commit)
        # branch
        refname_type="branch"
        short_refname=${refname##refs/heads/}
        ;;
    ...
    *)
        # Anything else (is there anything else?)
        echo >&2 "*** Unknown type of update to $refname ($rev_type)"
        echo >&2 "***  - no email generated"
        return 1
        ;;

现在来自generate_email的有用位。请查看实际脚本以获取详细信息,但在这种情况下,我们只关心对generate_email_header的调用,以及调用generate_update_branch_email的情况。

这是标题:

generate_email_header()
{
    # --- Email (all stdout will be the email)
    # Generate header
    cat <<-EOF
    To: $recipients
    Subject: ${emailprefix}$projectdesc $refname_type $short_refname ${change_type}d. $describe
    X-Git-Refname: $refname
    X-Git-Reftype: $refname_type
    X-Git-Oldrev: $oldrev
    X-Git-Newrev: $newrev

    This is an automated email from the git hooks/post-receive script. It was
    generated because a ref change was pushed to the repository containing
    the project "$projectdesc".

    The $refname_type, $short_refname has been ${change_type}d
    EOF
}
在我们关注的情况下,{p> $change_typeupdate所以这样说:branch zorg updated.$describegit describe $newrev的输出,或者如果那是空的 - 即,git describe没有注释标签只使用$newrev$recipients$emailprefix$projectdesc是来自各种配置。)

generate_update_branch_email中,有很多东西可以计算和打印电子邮件,其中删除并添加了提交;然后结束于:

    echo "Summary of changes:"
    git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev

基本上,$refname形式的分支引用refs/heads/*(例如,refs/heads/zorg)已经update d(来自现有的$oldrev并非全是0,对于不是全0的新$newrev,意味着所述(长形式)分支名称,人们通常称之为$short_refnamezorg),移动了那个分支小费。这比代码确定移动意味着什么更简单!

在您的情况下,您不必担心分支创建;你可以像定期更新那样对待它。您可能(或可能不想)想要对分支删除做一些特殊操作。大多数情况下,您只关心分支更新...而您想要做的就是,如果staging已更新,请更新您的特殊分段副本;如果master已更新,请更新您的特殊master副本。在每种情况下,更新都将是新的分支提示&#34;,这非常容易访问。

所以,如果我们把所有这些放在一起,我们得到以下(未经测试但非常简单)的钩子,我已经添加了从命令行调用它的能力。为了使它更有用,当从命令行调用时,您可以指定要在任何地方签出的修订版本,例如,您可以说./hookscript unused master~2 refs/heads/staging将rev master~2推入临时复制区域(假设你出于某种原因想要这样做)。

#! /bin/bash
#
# handle updates to our two interesting branches, staging and master.

# function to dump given commit state to target directory
# arguments: $1 - rev; $2 - target dir
copy_to_dir() {
    GIT_WORK_TREE="$2" git checkout -q -f "$1"
}

# function to handle an update to staging branch.
# arguments: $1 - rev to check out
update_staging() {
    copy_to_dir $1 /path/to/staging/site/webroot
}

# function to handle an update to master branch.
update_master() {
    copy_to_dir $1 /var/www
}

# function to handle one reference-change.
# arguments:
#    $1 - old revision, or all-0s on create
#    $2 - new revision, or all-0s on removal
#    $3 - reference (refs/heads/*, refs/tags/*, etc)
refchange() {
    local oldrev="$1" newrev="$2" ref="$3"
    local deleted=false
    local short_revname

    if expr "$newrev" : '0*$' >/dev/null; then
        deleted=true
    elif ! git rev-parse "$newrev"; then
        return # git rev-parse already printed an error
    fi

    case $ref in
    refs/heads/staging|refs/heads/master)
        shortref=${refname#refs/heads/};;
    *)
        return;;
    esac

    # someone pushed a change to staging branch or master branch
    if $deleted; then {
        echo "WARNING: you've deleted branch $shortref"
        echo "are you sure you wanted to do that?"
        echo "The operating copy is still operating, and"
        echo "will be updated when the branch is re-created."
        } 1>&2
        return
    fi

    # update either the staging copy or the master copy
    update_$shortref "$newrev"
}

# main driver: update from input stream (if no arguments) or use arguments
case $# in
3)  refchange "$1" "$2" "$3";;
0)  while read oldrev newrev refname; do
        refchange $oldrev $newrev $refname
    done;;
*)  echo "ERROR: update hook called with $# arguments, expected 0 or 3" 1>&2;;
esac

注意,因为我在已解析的版本上使用git checkout -q -f(而不是像stagingmaster这样的名称),这将把推送分支带到& #34;分离的HEAD&#34;每次都说明。更重要的是,如果您使用来自&#34; normal&#34;的命令行技巧。在Web服务器上的repo,它将取消您当前的分支。这不是致命的,但很容易令人讨厌。为避免这种情况,请将copy_to_dir的内容替换为git archive $1 | (rm -rf "$2" && mkdir "$2" && cd "$2" && tar xf -)

(通常情况下,您正在推动--bare回购,并且改变其当前分支&#34;的想法不是问题,因为没有人关心它。)