使用Git push部署项目


412

是否可以使用部署网站git push?我有一种预感,它与使用git钩子git reset --hard在服务器端执行a 有关,但是我将如何实现呢?


2
我猜测这仅适用于只有一台生产服务器的情况,对吗?
里克

6
@Rijk好吧,您可以使用Git同时推送到多台服务器,但是一旦达到该级别,您可能想要一个实际的解决方案,而不是像这样的黑客。
凯尔·克罗宁

我在项目中使用capistrano取得了成功,尽管该项目最初是为Ruby on Rails应用程序部署而设计的,但与PHP和其他项目均能很好地工作。

在ru.so上将答案翻译成俄语:ru.stackoverflow.com/questions/428483/…–
Nick Volynkin

Answers:


287

我在此站点上找到了此脚本,它似乎运行良好。

  1. 将您的.git目录复制到Web服务器
  2. 在本地副本上,修改.git / config文件,并将Web服务器添加为远程服务器:

    [remote "production"]
        url = username@webserver:/path/to/htdocs/.git
    
  3. 在服务器上,用此文件替换.git / hooks / post-update (在下面的答案中)

  4. 添加对文件的执行访问权限(同样,在服务器上):

    chmod +x .git/hooks/post-update
    
  5. 现在,只需在本地推送到您的Web服务器,它将自动更新工作副本:

    git push production
    

128
确保您具有.htaccess策略,以防止读取.git目录。如果有人觉得URL跳水,可以访问整个源代码,如果可以访问的话。
杰夫·

39
或者,只需将公共目录设置为git repo的子目录。然后,您可以拥有私人文件,可以确保不会被公开。
tlrobinson

3
该链接已死。是否有另一个指向更新后文件的链接?
罗伯特·赫斯特

6
也许我失去了一些东西,但不会你希望你的生产服务器(县)从主Git仓库producttion分支。我猜OP只有一台服务器?我通常使连续集成服务器进行站点的部署(在部署之前运行一些测试)。
亚当·根特

4
从已经具有一系列提交的存储库中执行这些步骤;起初您不能推送,因为master分支已经被检出。然后,如果您检出遥控器上的备用分支,则仅将不同的文件检出到工作目录中。我希望钩子可以重置
很难-barrymac 2011年

80

使用下面的更新后文件:

  1. 将您的.git目录复制到Web服务器
  2. 在本地副本上,修改.git / config文件,并将Web服务器添加为远程服务器:

    [remote "production"]
        url = username@webserver:/path/to/htdocs/.git
    
  3. 在服务器上,将.git / hooks / post-update替换为以下文件

  4. 添加对文件的执行访问权限(同样,在服务器上):

    chmod +x .git/hooks/post-update
    
  5. 现在,只需在本地推送到您的Web服务器,它将自动更新工作副本:

    git push production
    
#!/bin/sh
#
# This hook does two things:
#
#  1. update the "info" files that allow the list of references to be
#     queries over dumb transports such as http
#
#  2. if this repository looks like it is a non-bare repository, and
#     the checked-out branch is pushed to, then update the working copy.
#     This makes "push" function somewhat similarly to darcs and bzr.
#
# To enable this hook, make this file executable by "chmod +x post-update". 
git-update-server-info 
is_bare=$(git-config --get --bool core.bare) 
if [ -z "$is_bare" ]
then
      # for compatibility's sake, guess
      git_dir_full=$(cd $GIT_DIR; pwd)
      case $git_dir_full in */.git) is_bare=false;; *) is_bare=true;; esac
fi 
update_wc() {
      ref=$1
      echo "Push to checked out branch $ref" >&2
      if [ ! -f $GIT_DIR/logs/HEAD ]
      then
             echo "E:push to non-bare repository requires a HEAD reflog" >&2
             exit 1
      fi
      if (cd $GIT_WORK_TREE; git-diff-files -q --exit-code >/dev/null)
      then
             wc_dirty=0
      else
             echo "W:unstaged changes found in working copy" >&2
             wc_dirty=1
             desc="working copy"
      fi
      if git diff-index --cached HEAD@{1} >/dev/null
      then
             index_dirty=0
      else
             echo "W:uncommitted, staged changes found" >&2
             index_dirty=1
             if [ -n "$desc" ]
             then
                   desc="$desc and index"
             else
                   desc="index"
             fi
      fi
      if [ "$wc_dirty" -ne 0 -o "$index_dirty" -ne 0 ]
      then
             new=$(git rev-parse HEAD)
             echo "W:stashing dirty $desc - see git-stash(1)" >&2
             ( trap 'echo trapped $$; git symbolic-ref HEAD "'"$ref"'"' 2 3 13 15 ERR EXIT
             git-update-ref --no-deref HEAD HEAD@{1}
             cd $GIT_WORK_TREE
             git stash save "dirty $desc before update to $new";
             git-symbolic-ref HEAD "$ref"
             )
      fi 
      # eye candy - show the WC updates :)
      echo "Updating working copy" >&2
      (cd $GIT_WORK_TREE
      git-diff-index -R --name-status HEAD >&2
      git-reset --hard HEAD)
} 
if [ "$is_bare" = "false" ]
then
      active_branch=`git-symbolic-ref HEAD`
      export GIT_DIR=$(cd $GIT_DIR; pwd)
      GIT_WORK_TREE=${GIT_WORK_TREE-..}
      for ref
      do
             if [ "$ref" = "$active_branch" ]
             then
                   update_wc $ref
             fi
      done
fi

5
真是的...只是用您用于开发的语言编写该脚本,无论它是php,python,groovy还是其他任何语言!我从来没有理解过对shell脚本的热爱,因为shell脚本(主观地)具有非常奇怪的语法和很少的功能。
dVaffection 2014年

4
@dVaffection无论如何,如果您使用git,您将要编写shell命令。因此,与其写另一种语言的脚本,不如不断地在该语言和外壳程序之间切换。用shell编写所有内容似乎合乎逻辑吗?
Abderrahmane TAHRI JOUTI

我还必须在服务器上执行“ git config receive.denyCurrentBranch updateInstead”,以便它可以接受推送。我认为是因为分支已签出?
stackPusher

60

经过许多错误的开始和死胡同之后,由于本文的帮助,我终于可以使用“ git push remote ” 部署网站代码。

作者的更新后脚本只有一行,而他的解决方案不需要.htaccess配置即可像其他一些脚本一样隐藏Git存储库。

如果要在Amazon EC2实例上进行部署,则有两个绊脚石;

1)如果使用sudo创建裸目标存储库,则必须将存储库的所有者更改为ec2-user,否则推送将失败。(尝试“ chown ec2-user:ec2-user存储库”。)

2)如果您没有在/ etc / ssh / ssh_config中将Identity -private-key .pem 的位置预先配置为IdentityFile参数,或者在〜/ .ssh / config中使用“ [主机]-主机名-IdentityFile-用户”布局,此处描述...

...但是,如果在〜/ .ssh / config中配置了Host且与HostName不同,则Git推送将失败。(这可能是一个Git错误)


我按照您提到的文章中的步骤进行了操作,所有工作都像个魅力。我只想知道在安全性或稳定性方面是否存在一些缺点。有什么建议吗?
xlttj 2011年

xl-t:假设您通过SSH使用Git,我会说危险在于错误地使用Git。您可以问文章的作者;他以“欢迎提出问题和建议”结尾。我目前的(死脑)复制策略是使用Panic Software的Transmit。
Earl Zedd

1
使用挂钩时,链接的文章有一项重要要求。如果.git与工作目录使用相同的命名方案,则挂钩将失败。即/ foo / bar(工作目录)和/foo/bar.git(准系统git存储库)。因此,请确保将/ foo / bar重命名为其他名称,例如/foo/bar.live或/ foo / blah好吧,如果您想知道,如果您的工作目录与以下名称相同,则会收到确切的错误消息准系统存储库是“远程:致命的:无法跳回原始cwd:没有这样的文件或目录”
Antony 2012年

1
我不理解为什么您需要部署后挂钩才能运行。将代码更改推送到远程存储库意味着远程存储库是最新的。我想念什么?
Charlie Schliesser 2012年

1
@CharlieS您缺少的是git不允许您将分支推送到已签出该分支的存储库。在这种情况下,(恕我直言,非常好)答案是有两个存储库:您推送到的裸仓库和第二个仓库,当将裸仓库推入仓库时,其工作目录通过挂钩进行更新。
本·休斯

21

不要在服务器上安装git或在其中复制.git文件夹。要从git clone更新服务器,可以使用以下命令:

git ls-files -z | rsync --files-from - --copy-links -av0 . user@server.com:/var/www/project

您可能必须删除从项目中删除的文件。

这将复制所有签入的文件。rsync使用始终安装在服务器上的ssh。

您在服务器上安装的软件越少,他的安全性就越高,就越容易管理其配置和记录该文档。也无需在服务器上保留完整的git clone。这只会使正确保护所有内容变得更加复杂。


3
一个警告:它将同步您在工作目录中的文件。我认为可以使用隐藏当前更改,清除所有内容,部署然后还原存储的脚本来避免这种情况。
mateusz.fiolka,2012年

服务器是男性?
伊恩·沃伯顿

12

本质上,您需要做的是:

server = $1
branch = $2
git push $server $branch
ssh <username>@$server "cd /path/to/www; git pull"

我在应用程序中将这些行作为可执行文件称为deploy

因此,当我要进行部署时,请输入./deploy myserver mybranch


如果您需要ssh的其他私钥或用户名,请参阅我的解答该如何解决问题
Karussell 2011-11-26

部署到多台服务器时,该解决方案比我自己的解决方案要快!只需推入主仓库并从中并行拉出即可。如果您不希望或无法将密钥部署到每个实例,请使用密钥代理!ssh -A ...
卡苏尔2012年

1
如果您包括一个设置SSH密钥的指南,该指南将可以“无缝地”工作,那就更容易了
Hengjie

git pull对于自动化部署,应避免使用,因为如果有任何冲突,它的合并部分可能需要手动清理。
Quinn Comendant 2015年

9

我这样做的方法是,在我的部署服务器上有一个裸露的Git存储库,用于推送更改。然后,我登录到部署服务器,更改为实际的Web服务器docs目录,然后执行git pull。我不使用任何钩子来尝试自动执行此操作,这似乎比它值得的麻烦更多。


如果新代码中有错误,您是按每次提交还是整个拉动重置?(或者只有一种可能?)
鲁迪

1
@Rudie:如果需要回滚部署服务器上的更改,则可以使用git reset来回退最新的更改(所有提交,而不仅仅是整个请求)。如果您需要回滚不是最新提交的特定内容,则可以使用,git revert但应该仅在紧急情况下使用(git revert创建一个新的提交以撤消某些先前提交的影响)。
格雷格·休吉尔

只是出于好奇:您为什么认为钩子会比它值得的麻烦更多?
治(Rijk)2012年

@Rijk:为此使用钩子时,实际的Web服务器docs目录由自动后台进程更改。登录使我可以更好地控制将更改应用于docs目录的确切时间。而且,在出现问题时更容易修复。如果提交者没有足够的权限登录到Web服务器,则挂钩可能更合适。
Greg Hewgill 2012年

那么您实际的webapp文件夹也是.git存储库吗?那.git文件夹怎么办?它对外界是可见的?
Fernando

9

git config --local receive.denyCurrentBranch updateInstead

在Git 2.3中添加了这,这是一个很好的可能性:https : //github.com/git/git/blob/v2.3.0/Documentation/config.txt#L2155

您在服务器存储库上进行了设置,并且如果它是干净的,它也会更新工作树。

2.4 push-to-checkout对未出生分支钩和处理有了进一步的改进。

用法示例:

git init server
cd server
touch a
git add .
git commit -m 0
git config --local receive.denyCurrentBranch updateInstead

cd ..
git clone server local
cd local
touch b
git add .
git commit -m 1
git push origin master:master

cd ../server
ls

输出:

a
b

确实有GitHub公告中提到的以下缺点:

  • 您的服务器将包含一个.git目录,其中包含项目的整个历史记录。您可能要特别确保不能将其提供给用户!
  • 在部署期间,用户可能会以不一致的状态暂时访问该站点,其中某些文件为旧版本,另一些文件为新版本,甚至是半写文件。如果这对您的项目来说是个问题,则按需部署可能不适合您。
  • 如果您的项目需要“构建”步骤,那么您可能必须通过githooks对其进行显式设置。

但是所有这些要点都不在Git的范围之内,必须由外部代码来处理。因此,从这个意义上讲,这与Git挂钩一起是最终的解决方案。


要设置它,运行这个命令:“git的配置receive.denyCurrentBranch updateInstead”在终端
stackPusher

5

更新:我现在将Lloyd Moore解决方案与关键代理一起使用ssh -A ...。推送到主存储库,然后从所有计算机并行从中拉出,速度会稍快一些,并且需要在这些计算机上进行较少的设置。


在这里看不到这个解决方案。如果服务器上安装了git,只需通过ssh推送即可。

您需要在本地.git / config中输入以下内容

[remote "amazon"]
    url = amazon:/path/to/project.git
    fetch = +refs/heads/*:refs/remotes/amazon/*

但是,嘿,那是amazon:怎么回事?在本地〜/ .ssh / config中,您需要添加以下条目:

Host amazon
    Hostname <YOUR_IP>
    User <USER>
    IdentityFile ~/.ssh/amazon-private-key

现在你可以打电话

git push amazon master
ssh <USER>@<YOUR_IP> 'cd /path/to/project && git pull'

(顺便说一句:/path/to/project.git与实际的工作目录/ path / to / project不同)


5

对于部署方案

在我们的场景中,我们将代码存储在github / bitbucket上,并希望部署到实时服务器上。在这种情况下,以下组合对我们有效(这是此处高度支持的答案的重新组合

  1. .git目录复制到Web服务器
  2. 在本地副本上 git remote add live ssh://user@host:port/folder
  3. 在远程: git config receive.denyCurrentBranch ignore
  4. 远程:nano .git/hooks/post-receive并添加以下内容:

    #!/bin/sh GIT_WORK_TREE=/var/www/vhosts/example.org git checkout -f

  5. 在远程: chmod +x .git/hooks/post-receive

  6. 现在您可以用 git push live

笔记

  • 此解决方案适用于较旧的git版本(已通过1.7和1.9测试)
  • 您需要确保先推送到github / bitbucket,这样您才能实时获得一致的存储库
  • 如果您的.git文件夹位于文档根目录中,请确保通过添加到.htaccesssource)将其隐藏在外部:

    RedirectMatch 404 /\..*$


4

我们使用capistrano来管理部署。我们构建了capistrano以部署在登台服务器上,然后与我们所有的服务器运行rsync。

cap deploy
cap deploy:start_rsync (when the staging is ok)

使用capistrano,我们可以在发生错误的情况下轻松回滚

cap deploy:rollback
cap deploy:start_rsync

您是否通过rsync将实时部署集成到了capistrano中?
马丁·亚伯拉罕


1

听起来您的服务器上应该有两个副本。可以推送/拉取的原始副本,完成后将推送更改,然后将其克隆到您的Web目录中,并设置cronjob以每天从您的Web目录中更新git pull或所以。


1

您可以想像地设置了一个git钩子,当说一次提交时说“稳定”分支时,它将提取更改并将其应用到PHP站点。最大的缺点是,如果出现问题,您将不会有太多的控制权,这会增加测试时间-但您可以将合并后的主干分支合并为稳定分支,从而知道需要进行多少工作您可能会遇到多少冲突。除非您只打算只运行一个站点,否则请务必注意站点特定的任何文件(例如,配置文件)。

或者,您是否考虑过将更改推送到网站?

有关git hooks的信息,请参见githooks文档。


1

我对基督徒的解决方案的看法。

git archive --prefix=deploy/  master | tar -x -C $TMPDIR | rsync $TMPDIR/deploy/ --copy-links -av username@server.com:/home/user/my_app && rm -rf $TMPDIR/deploy
  • 将主分支归档到tar中
  • 将tar存档提取到系统temp文件夹中的deploy dir中。
  • rsync更改到服务器
  • 从临时文件夹中删除deploy目录。

1

我正在使用toroid.org提供的以下解决方案,该解决方案具有更简单的挂钩脚本。

在服务器上:

$ mkdir website.git && cd website.git
$ git init --bare
Initialized empty Git repository in /home/ams/website.git/

并将钩子安装在服务器上:

$ mkdir /var/www/www.example.org
$ cat > hooks/post-receive
#!/bin/sh
GIT_WORK_TREE=/var/www/www.example.org git checkout -f
GIT_WORK_TREE=/var/www/www git clean -f -d # clean directory from removed files

$ chmod +x hooks/post-receive

在您的客户上:

$ mkdir website && cd website
$ git init
Initialized empty Git repository in /home/ams/website/.git/
$ echo 'Hello, world!' > index.html
$ git add index.html
$ git commit -q -m "The humble beginnings of my web site."

$ git remote add web ssh://server.example.org/home/ams/website.git
$ git push web +master:refs/heads/master

然后发布,只需键入

$ git push web

网站上有完整的描述:http : //toroid.org/ams/git-website-howto


这样,请勿删除存储库中的现有文件。
RusAlex

2
为什么git push web +master:refs/heads/master不只是git push web master
Matthieu Moy 2015年

1

作为补充性回答,我想提供一种替代方法。我正在使用git-ftp,它工作正常。

https://github.com/git-ftp/git-ftp

易于使用,只需输入:

git ftp push

git会自动上传项目文件。

问候


0

在您有多个开发人员访问同一存储库的环境中,以下准则可能会有所帮助。

确保您拥有所有开发人员所属的unix组,并将.git存储库的所有权授予该组。

  1. 在服务器存储库的.git / config中,设置sharedrepository = true。(这告诉git允许提交和部署所需的多个用户。

  2. 将每个用户的bashrc文件中的umask设置为相同-002是一个好的开始


0

我最终创建了自己的基本部署工具,该工具会自动从存储库中提取新更新-https: //github.com/jesalg/SlimJim-基本上,它会监听github post-receive-hook,并使用代理触发更新脚本。


0

我对接收后挂钩使用两种解决方案:

部署解决方案1

#!/bin/bash 
#  /git-repo/hooks/post-receive - file content on server (chmod as 755 to be executed)
# DEPLOY SOLUTION 1 

    export GIT_DIR=/git/repo-bare.git
    export GIT_BRANCH1=master
    export GIT_TARGET1=/var/www/html
    export GIT_BRANCH2=dev
    export GIT_TARGET2=/var/www/dev
    echo "GIT DIR:  $GIT_DIR/"
    echo "GIT TARGET1:  $GIT_TARGET1/"
    echo "GIT BRANCH1:  $GIT_BRANCH1/"
    echo "GIT TARGET2:  $GIT_TARGET2/"
    echo "GIT BRANCH2:  $GIT_BRANCH2/"
    echo ""

    cd $GIT_DIR/

while read oldrev newrev refname
do
    branch=$(git rev-parse --abbrev-ref $refname)
    BRANCH_REGEX='^${GIT_BRANCH1}.*$'
    if [[ $branch =~ $BRANCH_REGEX ]] ; then
        export GIT_WORK_TREE=$GIT_TARGET1/.
        echo "Checking out branch: $branch";
        echo "Checking out to workdir: $GIT_WORK_TREE"; 

        git checkout -f $branch
    fi

    BRANCH_REGEX='^${GIT_BRANCH2}.*$'
    if [[ $branch =~ $BRANCH_REGEX ]] ; then
        export GIT_WORK_TREE=$GIT_TARGET2/.
        echo "Checking out branch: $branch";
        echo "Checking out to workdir: $GIT_WORK_TREE"; 

        git checkout -f $branch
    fi
done

部署解决方案2

#!/bin/bash 
#  /git-repo/hooks/post-receive - file content on server (chmod as 755 to be executed)
# DEPLOY SOLUTION 2

    export GIT_DIR=/git/repo-bare.git
    export GIT_BRANCH1=master
    export GIT_TARGET1=/var/www/html
    export GIT_BRANCH2=dev
    export GIT_TARGET2=/var/www/dev
    export GIT_TEMP_DIR1=/tmp/deploy1
    export GIT_TEMP_DIR2=/tmp/deploy2
    echo "GIT DIR:  $GIT_DIR/"
    echo "GIT TARGET1:  $GIT_TARGET1/"
    echo "GIT BRANCH1:  $GIT_BRANCH1/"
    echo "GIT TARGET2:  $GIT_TARGET2/"
    echo "GIT BRANCH2:  $GIT_BRANCH2/"
    echo "GIT TEMP DIR1:  $GIT_TEMP_DIR1/"
    echo "GIT TEMP DIR2:  $GIT_TEMP_DIR2/"
    echo ""

    cd $GIT_DIR/

while read oldrev newrev refname
do
    branch=$(git rev-parse --abbrev-ref $refname)
    BRANCH_REGEX='^${GIT_BRANCH1}.*$'
    if [[ $branch =~ $BRANCH_REGEX ]] ; then
        export GIT_WORK_TREE=$GIT_TARGET1/.
        echo "Checking out branch: $branch";
        echo "Checking out to workdir: $GIT_WORK_TREE"; 

        # DEPLOY SOLUTION 2: 
        cd $GIT_DIR/; mkdir -p $GIT_TEMP_DIR1; 
        export GIT_WORK_TREE=$GIT_TEMP_DIR1/.
        git checkout -f $branch
        export GIT_WORK_TREE=$GIT_TARGET1/.
        rsync $GIT_TEMP_DIR1/. -v -q --delete --delete-after -av $GIT_TARGET1/.
        rm -rf $GIT_TEMP_DIR1
    fi

    BRANCH_REGEX='^${GIT_BRANCH2}.*$'
    if [[ $branch =~ $BRANCH_REGEX ]] ; then
        export GIT_WORK_TREE=$GIT_TARGET2/.
        echo "Checking out branch: $branch";
        echo "Checking out to workdir: $GIT_WORK_TREE"; 

        # DEPLOY SOLUTION 2: 
        cd $GIT_DIR/; mkdir -p $GIT_TEMP_DIR2; 
        export GIT_WORK_TREE=$GIT_TEMP_DIR2/.
        git checkout -f $branch
        export GIT_WORK_TREE=$GIT_TARGET2/.
        rsync $GIT_TEMP_DIR2/. -v -q --delete --delete-after -av $GIT_TARGET2/.
        rm -rf $GIT_TEMP_DIR2
    fi
done

两种解决方案均基于该线程中可用的早期解决方案。

请注意,BRANCH_REGEX ='^ $ {GIT_BRANCH1}。$'会过滤与“ master ”或“ dev *”字符串匹配的分支名称,如果推送的分支匹配,则会部署工作树。这样就可以将开发版本和主版本部署到不同的位置。

部署解决方案1仅删除文件,这些文件是存储库的一部分,并且已通过提交删除。它比部署解决方案2更快。

DEPLOY SOLUTION 2的优点是,它将从生产目录中删除任何新文件,无论该文件是否已添加到仓库中,该文件都已在服务器端添加。这将永远是干净的仓库。它比部署解决方案1慢。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.