2

これは意図しない目的で Git を使用するカテゴリに分類されると確信していますが、専門家がコメントできるように、私が行っていることを共有したかったのです。

サイトの 2 つのバージョンを同時に実行できるように、ライブ サーバーで仮想ホスティングを使用しています。1 つは example.com で、もう 1 つは staging.example.com です。ステージング サイトは新機能のテストに使用され、サイトの git リポジトリの 2 つのブランチ (たとえばstagingmaster ) をそれぞれのサイト ルートにリンクする方法を作成しました。

まず、プッシュ時に最新のマスターを Web ルートに自動的にチェックアウトできるように、リモート サーバーに git をセットアップします (この優れた手法を使用します)。

次に、post-receive フックに次のように記述します。

#!/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 つのブランチを使用して、サイトの 2 つのバージョンを維持できます)。プッシュすると、ステージング ブランチの新しい変更でステージング サイトが更新され、マスター サイトも同様になります。

新機能を公開する前にデモを管理するには、これが優れた方法であることがわかりました。

私はこれをやるべきではありませんか?より良い方法はありますか?その他のアイデアや懸念事項はありますか?

ありがとう、ジェイソン

4

2 に答える 2

4

私の意見では、あなたは Git が使用されるべき方法で Git を使用しています。同じファイルの 2 つのバージョンがあり、それぞれの「バージョン」をブランチとして別々に管理したいとします。

ステージングからマスターへの変更をマージする準備ができたら、ブランチ内でgit merge staging/のようなことをしてそこから移動するだけだと思います。git rebase stagingmaster

Git の使い方はまったく間違っていません。

于 2012-04-07T22:01:40.517 に答える
3

あなたはよりおしゃれになる方法を知りたがっていました。受信後メール フック スクリプトを調べて、それがどのように機能するか (一部) を示します。これはかなり長くなります!:-)

OK、それで、ここにpost-receieve-email(少し再フォーマットされた)からの重要なビットがあります:

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

これにより、入力ストリームから古い SHA、新しい SHA、および ref 名が読み取られます (これは git の失敗の 1 つと考えられます。このストリームは 1 回しか読み取ることができず、その後は消えてしまいます。これにより、関連性のない多数のフックが過剰に接続されます。難しい)、2 つのシェル関数を呼び出してそれらを調べてから、それぞれを無視するか、何らかの処理を行います。

ここで、prep_for_emailシェル関数の重要な部分とコメントを示します。

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

ここでの rev-parse (および、コマンドライン引数を許可する上で削除したコード) を使用すると、"HEAD" や "HEAD^" などの rev-name をフィードできます。実際の post-receive フックは常に生の SHA1 を取得するため、rev-parse 呼び出しはノーオペレーションです。

    # --- 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

post-receive フックで、「古い」SHA1 が0000000000000000000000000000000000000000(すべてゼロ、つまり 40 個の 0;expr上記はすべてゼロをチェックするだけ) の場合、これは「ref」引数が以前には存在しなかったことを意味し、現在は存在します。これは通常、ブランチ作成操作ですが、タグ作成にすることもできます。一方、「新しい」SHA1 がすべてゼロの場合、以前は「ref」引数存在していましたが、現在は存在しません。通常はブランチ削除操作です。それ以外は、ref-nameが old-rev に解決されるために使用され、現在は new-rev に解決されます。これは通常、ブランチの更新ですが、たとえばタグの移動の場合もあります。

次に、電子メール フックには、git リポジトリ内の実際の基になるオブジェクト タイプに対して参照名をクロスチェックする、優れた「パラノイア スタイル」のプログラミングがあります。

    # --- 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

ref-name にフォームがrefs/tags/*あり、更新が注釈付きタグに対するものである場合、これは と の両方$oldrev_type$newrev_typeに設定する必要がありtagます。(軽量タグの場合、代わりに両方になりcommitます。軽量タグが注釈付きタグに変わった場合、古いタイプはコミットになり、新しいタイプはタグになります。) そしてもちろん、削除する場合ブランチまたはタグの場合、「新しい」リビジョンはすべて 0 になり、単に失敗する$newrev_typeため空の文字列になります(これが.git cat-file -t2> /dev/null

(余談ですが、引数を引用する正当な理由git cat-file -tはありませんし、引用しない人もいます。おそらく、通常の空の文字列の引数の問題に遭遇した後、引用に過度に満足している誰かが、1つを逃しただけです。幸い、この場合はどちらの方法でも無害です. :-) )

電子メール スクリプトには、非常に長い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
}

$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

基本的に、 (すべて 0 ではない既存のものからすべて 0 ではない新しいものへ) され$refnameた形式refs/heads/*(たとえば、refs/heads/zorg)のブランチ参照は、その (長い形式の) ブランチ名を意味します。通常は( ) として参照されますが、その分岐先を移動しました。これは、その動きが何を意味するかを正確に理解するためのコードよりもはるかに簡単です!update$oldrev$newrev$short_refnamezorg

あなたの場合、ブランチの作成について心配する必要はありません。通常の更新のように扱うことができます。ブランチの削除で何か特別なことをしたい (またはしたくない) かもしれません。ほとんどの場合、ブランチの更新だけに関心があります。必要なのは、staging更新されている場合は、特別なステージング コピーを更新することだけです。が更新されている場合は、特別なコピーmasterを更新します。masterいずれの場合も、更新は「新しいブランチのヒントへ」であり、非常に簡単にアクセスできます。

したがって、これらすべてをまとめると、コマンド ラインから呼び出す機能を追加した次の (テストされていませんが、かなり些細な) フックが得られます。より便利にするために、コマンドラインから呼び出されたときに、チェックアウトしたいリビジョンをどこにでも指定でき./hookscript unused master~2 refs/heads/stagingます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

私は解析済みの rev を使用しているため ( orgit checkout -q -fのような名前ではなく)、プッシュ先のブランチが毎回「切り離された HEAD」状態になることに注意してください。さらに重要なことは、Web サーバー上の「通常の」レポからコマンドライン トリックを使用すると、現在のブランチの係留が解除されることです。これは致命的ではありませんが、簡単に迷惑になる可能性があります。これを避けるには、 の内容を.stagingmastercopy_to_dirgit archive $1 | (rm -rf "$2" && mkdir "$2" && cd "$2" && tar xf -)

(通常、リポジトリにプッシュし、--bare「現在のブランチ」の考え方を変更しても問題ありません。誰も気にしないからです。)

于 2012-04-08T20:52:34.260 に答える