使用Git push部署项目

418

使用git push是否可以部署网站?我有一种预感这与使用git hooks在服务器端执行git reset --hard有关,但我该如何完成它呢?


3
这仅适用于仅有一个生产服务器的情况,是吗? - Rijk
6
嗯,你可以用Git同时推送到多个服务器,但是一旦你达到了这个级别,你可能需要一个实际的解决方案,而不是像这样的hack。 - Kyle Cronin
我在我的项目中使用 capistrano 取得了成功,尽管它最初是为 Ruby on Rails 应用程序部署而设计的,但它也可以很好地与 PHP 和其他项目配合使用。 - user1559306
在ru.so上将答案翻译成俄语:http://ru.stackoverflow.com/questions/428483/git-настройка-и-развертка-проекта - Nick Volynkin
19个回答

290

我在这个脚本上找到了并且在这个网站上,它似乎工作得非常好。

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

  3. [remote "production"]
        url = username@webserver:/path/to/htdocs/.git
    
  4. 在服务器上,使用这个文件替换.git/hooks/post-update

  5. 给该文件添加执行权限(同样在服务器上):

  6. chmod +x .git/hooks/post-update
    
    现在,只需将更改推送到您的Web服务器,它就应该自动更新工作副本:
    git push production
    

129
确保你的 .htaccess 文件设置了策略,以防止 .git 目录被读取。如果这个目录可以访问,那么任何想要探测 URL 的人都可能能轻易地获取到整个源代码。 - Jeff Ferland
39
或者,只需将公共目录设置为Git存储库的子目录。这样,您就可以拥有私人文件,确保它们不会被公开。 - tlrobinson
3
链接失效了,有另一个更新文件的链接吗?请提供。 - Robert Hurst
6
也许我有所遗漏,但是你不希望你的生产服务器从主 Git 仓库的生产分支中拉取吗?我猜楼主只有一个服务器?通常我会让我的持续集成服务器在部署网站之前运行一些测试(进行部署)。 - Adam Gent
4
在遵循一个已经有一系列提交记录的代码库的步骤后,首先你无法推送(push),因为主分支已被签出(checked out)。如果你在远程上检出一个替代分支,则只有不同的文件会被签出到工作目录中。我期望这个钩子可以为我执行 "reset --hard" 操作。 - barrymac
显示剩余12条评论

85

使用以下的post-update文件:

  1. 将你的.git目录复制到你的web服务器上
  2. 在本地副本上,修改你的.git/config文件并将你的web服务器作为一个远程仓库添加:

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

  4. 给该文件添加执行权限(同样在服务器上):

  5. chmod +x .git/hooks/post-update
    
    现在,只需将更改推送到您的 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脚本,因为它们(就主观而言)具有相当奇怪的语法和很少的功能特性。 - Dmitry
8
无论如何,如果你在使用 Git,你都需要编写 shell 命令。因此,不要用另一种语言编写脚本并不断在该语言和 shell 之间转换,将所有内容都编写成 shell 脚本似乎更合理,你觉得呢? - Abderrahmane TAHRI JOUTI
我不得不在服务器上执行“git config receive.denyCurrentBranch updateInstead”,以便它接受推送。我认为这是因为分支被检出了? - stackPusher

61
经过多次失败和死胡同,我终于能够通过“git push remote”轻松部署网站代码,多亏了这篇文章
作者的post-update脚本只有一行,他的解决方案不需要.htaccess配置来隐藏Git repo,而其他一些解决方案则需要。
如果您在Amazon EC2实例上部署此解决方案,则会遇到一些问题:
1)如果您使用sudo创建裸目标存储库,则必须更改存储库的所有者为ec2-user,否则推送将失败。(尝试“chown ec2-user:ec2-user repo.”)
2)如果您没有预先配置您的amazon-private-key.pem位置,推送将失败。您可以在/etc/ssh/ssh_config中作为IdentityFile参数或在~/.ssh/config中使用“[Host] - HostName - IdentityFile - User”布局进行配置,详情请参见此处...
...但是,如果在~/.ssh/config中配置的Host与HostName不同,则Git push将失败。(这可能是一个Git bug)

1
链接的文章在使用钩子时有一个重要的要求。如果.git恰好与工作目录具有相同的命名方案,则钩子将失败。例如,/foo/bar(工作目录)和/foo/bar.git(裸git存储库)。因此,请确保将/foo/bar重命名为其他名称,例如/foo/bar.live或/foo/blah。如果您的工作目录与裸存储库具有相同的名称,则会收到以下精确错误消息:“remote: fatal: Could not jump back into original cwd: No such file or directory”。 - Antony
1
我不明白为什么需要运行一个部署后钩子。将代码更改推送到远程存储库意味着远程存储库是最新的。我错过了什么吗? - Charlie Schliesser
1
@CharlieS,你所忽略的是git不允许你将一个分支推送到已经检出该分支的仓库。在这种情况下,(我认为非常好的)解决方案是拥有两个仓库:一个裸仓库用于推送,另一个仓库通过钩子更新工作目录,当裸仓库被推送时。 - Ben Hughes
非常好的答案。比我找到的任何其他答案都要干净。 - Ben Hughes
最简单的解决方案。这应该是得票最高并被接受的答案。 - Oliver Angelil
显示剩余2条评论

21
不要在服务器上安装git或将.git文件夹复制到那里。要从git克隆更新服务器,可以使用以下命令:

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

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

你可能需要删除已从项目中移除的文件。

这将复制所有被选中的文件。rsync 使用在服务器上已经安装的 ssh。

你在服务器上安装的软件越少,它就越安全,也更容易管理其配置和文档。并且没有必要在服务器上保留完整的 git 克隆。这只会使一切变得更加复杂,更难以正确地保护所有内容。


3
注意:这将同步您工作目录中的文件。我认为可以使用一个脚本来避免这种情况,脚本需要隐藏当前的更改内容,清除所有内容,部署然后恢复之前隐藏的更改内容。 - mateusz.fiolka
服务器是男性的吗? - Ian Warburton

13

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 hooks一起是终极解决方案。


要设置它,请在终端中运行以下命令:'git config receive.denyCurrentBranch updateInstead' - stackPusher
1
在我看来,这应该是最受欢迎的答案。如果这个一行命令可以通过设置一个git选项来解决问题,那么挖掘所有这些带有长shell脚本/钩子的答案总是如此疯狂。 - rugk

12

基本上你只需要做以下几点:

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

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

所以当我想要部署时,我键入 ./deploy myserver mybranch


如果您需要不同的私钥或用户名来进行SSH,请参阅我的答案以解决问题。 - Karussell
这个解决方案在部署到多台服务器时比我的自用方案更快!只需推送到主仓库并从中并行拉取即可。如果您不想或无法将密钥部署到每个实例,请使用密钥代理! ssh -A ... - Karussell
1
如果您提供了一个设置SSH密钥的指南,那么这个答案将更容易地实现“无缝”工作。 - Hengjie
应避免在自动化部署中使用 git pull,因为合并部分可能需要手动清理,如果存在任何冲突。 - Quinn Comendant

9

我个人的做法是在我的部署服务器上有一个裸的Git仓库,然后我将更改推送到这里。接着我登录进部署服务器,切换到实际的Web服务器文档目录,并执行git pull命令。我不使用任何钩子来自动化此过程,因为这样做似乎会带来更多麻烦。


1
@Rudie:如果你需要在部署服务器上回滚更改,那么可以使用git reset来向后移动到最新的更改(所有提交,而不仅仅是整个pull)。如果您需要回滚某些特定的东西,而不是最新的提交,那么可以使用git revert,但这应该只在紧急情况下使用( git revert创建一个新的提交,以撤消某个先前提交的影响)。 - Greg Hewgill
只是出于好奇:您认为钩子在这方面会带来的麻烦比它的价值还要大吗? - Rijk
@Rijk: 在这种情况下使用钩子,实际的Web服务器文档目录会被自动后台进程更改。登录可以让我更好地控制何时将更改应用于文档目录。此外,当出现问题时更易修复。如果提交者没有足够的访问权限登录到Web服务器,则可能更适合使用钩子。 - Greg Hewgill
所以你的实际网站文件夹也是一个.git仓库?那么.git文件夹呢,它对外界可见吗? - Fernando
是的,谢谢。比如说通过Apache配置很容易隐藏.git文件夹。 - Fernando
显示剩余2条评论

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 文件夹在文档根目录中,请确保通过将以下内容添加到 .htaccess来源)来从外部隐藏它:

    RedirectMatch 404 /\..*$


5
我们使用 capistrano 进行部署管理。我们使用 capistrano 在一个测试服务器上进行部署,然后使用 rsync 同步到所有的服务器上。
cap deploy
cap deploy:start_rsync (when the staging is ok)

使用Capistrano,我们可以轻松地回滚以解决漏洞问题。
cap deploy:rollback
cap deploy:start_rsync

你是否已经将通过rsync进行实时部署集成到Capistrano中了? - Martin Abraham

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 不同。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接