我确信这属于使用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中使用两个分支),当我推送时,登台站点会随着登台分支中的任何新更改而更新,并且与主站点相同。
我发现在将这些功能公开之前管理演示新功能是一种很好的方式。
我不应该这样做吗?有没有更好的办法?其他想法或顾虑?
谢谢, 杰森
答案 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_type
是update
所以这样说:branch zorg updated.
($describe
是git 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_refname
(zorg
),移动了那个分支小费。这比代码确定移动意味着什么更简单!
在您的情况下,您不必担心分支创建;你可以像定期更新那样对待它。您可能(或可能不想)想要对分支删除做一些特殊操作。大多数情况下,您只关心分支更新...而您想要做的就是,如果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
(而不是像staging
或master
这样的名称),这将把推送分支带到& #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;的想法不是问题,因为没有人关心它。)