如何通过git push将多个分支部署到不同的目录?

6

在生产环境中,我维护两个网站 - beta和release。每个网站通过软链接指向不同的目录(例如)

beta_public_html -> /home/scott/myapp/trunk/public
public_html      -> /home/scott/myapp/branches/1.2.3/public

我是一位长期使用svn的用户,现在要转向git。我习惯于通过svn update进行部署,并更改新分支上的软链接,这些都很简单。

现在我要使用git。我仍然需要两个软链接(这是一个使用Passenger的Rails应用程序),但现在我希望它们指向两个不同的git分支(例如“beta”和“release”)。并且我希望能够通过git push(或git pull)更新它们。

问题1:我不确定最佳的方法是什么。

我开始做的方式是只是部署到两个不同的远程,例如:

git push ssh://scott@x.com/home/scott/myapp-beta beta
git push ssh://scott@x.com/home/scott/myapp-release release

但是这样做不起作用,因为默认情况下push不会更新工作树。
所以我进入远程目录并运行git reset --hard第一次,它会拉取工作树。但是我再次推送,无法使新的推送显示出来 - 它仍停留在最初的状态。
(顺便说一句,注意我似乎无法推送到“myapp-beta.git” - 这会失败,我必须推送到目录名称。我担心这是问题的一部分,但我不知道我在这里做错了什么。)
所以,如果问题1的答案是我的方法正确,请问第2部分:我正在做什么有问题?如果有钩子应该使用,有人可以指引我吗?
(对于问题1的回答是“运行这七个手动步骤”的回答将不是一个非常有用的答案,因为svn checkout + ln -s是两个步骤。)
谢谢。我想回到编写代码。

你不喜欢源代码控制吗? - knittl
源代码控制是达到目的的手段,而不是目的本身。 - Josh Lee
好的,我不是在试图开始一场关于源代码控制的讨论。删除这个随意的评论。 - scottru
1
潜在的 post-receive 钩子(参见 http://www.kernel.org/pub/software/scm/git/docs/githooks.html)在裸仓库中可能会触发对“beta”和“release”仓库的推送。 - VonC
请参见 http://debuggable.com/posts/git-tip-auto-update-working-tree-via-post-receive-hook:49551efe-6414-4e86-aec6-544f4834cda3。 - VonC
刚刚添加了一个适用于您场景的 post-receive 钩子(尽管需要根据您的确切配置进行微调)。 - VonC
4个回答

3

这篇文章 Git push is worse than worsless 讨论了一个类似的问题,其中有一个有趣的解决方案和结论:

  • 在生产服务器上创建一个裸仓库
  • 克隆一个带有钩子的仓库,用于将推送到裸仓库中的内容拉取下来

因此,在您的情况下,

  • 一个裸仓库,您可以将beta和release分支推送到其中
  • 两个克隆仓库 'beta' 和 'release',带有钩子以从裸仓库中拉取各自的分支。

简而言之:一步完成:git push。不再需要管理链接(因为目录不再代表Git中的分支,不像SVN)


关于钩子部分,裸仓库中的 post-receive hook 可能是你所需要的全部内容。
请查看 Git Tip:通过 post-receive 钩子自动更新工作树
$ cd bare
$ chmod +x .git/hooks/post-receive

with a post-receive hook like

#!/bin/sh
cd ../../beta
env -i git reset --hard
cd ../../release
env -i git reset --hard

注意: post-receive钩子以GIT_DIR环境变量设置为repo/.git文件夹开始,因此无论您进入什么路径,它都将始终尝试在那里运行任何后续的git命令。 修复这个问题只需要取消设置GIT_DIR。 'env -i'可以做到这一点:它完全忽略继承的环境,并仅使用提供的变量和值。

谢谢,Von。我已经阅读了这篇文章,并理解了第一个解决方案,它实际上只涉及到远程端的拉取(有时还需要重置)。我会尝试解决这个问题,看看能否弄清楚它的工作原理。你有这些钩子的版本指针吗? - scottru
第二部分:这基本上是有效的 - 我不得不用“git pull”替换“git reset --hard”,但除此之外,它可以正常工作。(好吧,还有git的完整路径,只是为了记录。)感谢Von!我非常感激你的帮助。 - scottru

2
解决方案是将代码推送到单个存储库,该存储库将使用 updatepost-receive 钩子
有几种不同的可能性来创建两个检出,这些检出可以在挂钩(服务器上)中使用。从最轻量级开始:
  • If you don't need for those two checked out directories (checked out versions) to actually be git repositories, you can simply use git-archive to export two snapshots (two branches)

     git archive --format=tar --prefix=public_html/ master | (cd /var/www/html && tar xf -)
     git archive --format=tar --prefix=beta_public_html/ devel | (cd /var/www/html && tar xf -)
    

    where 'master' and 'devel' are names of branches that you wanted to have checked out. Note that --format=tar is not strictly speaking needed, as tar format is default for "git archive". You might also want to remove old contents ("rm -rf public_html/" before "tar xf -" in first line, joined with "&&", and similarly for the second line).

    Alternate way of exporting files would be to use low-level commands "git read-tree" (which writes tree to index) and "git checkout-index" (which copies files from index to working area).

    In this solution the repository you push into can (and should) be bare, i.e. without working directory itself.

  • Another solution would be for the repository you push into to have two working directories, which can be created using git-new-workdir script from contrib/workdir. Each of those working areas would have be a checkout of appropriate branch of this repository.

    Then update or post-receive hook would unset GIT_DIR, go to those working areas (public_html and beta_public_html), and do "git reset --hard" there.

    In this solution "checkouts" would have some git-related metainfo in them (in hidden .git directory).

  • Yet another solution would be to have two (additional) slave repositories. Pushing into main (master) repository would then (via hook) either push into those two slave repositories, where their hooks would do "git reset --hard" or equivalent, or go to those two slave repositories and do a "git pull" from master there.

    Those two slave repositories would be non-bare, and can be [directly in] public_html and beta_public_html. In this solution "checkouts" would be full-fledged git repositories itself.

    You can improve this setup by having those slave repositories to have "alternates" (alternate object database) to point to master repository (i.e. be cloned with "git clone --shared ..."); without this object database in slaves starts hardlinked to master. This of course is possible only if master and slaves are on the same filesystem.

    Note this solution allows for master repository to be on different host than slave repositories. (although I guess this is flexibility you don't need).

  • Finally you can instead of current setup deploy gitweb or some other git web interface (see InterfacesFrontendsAndTools and Gitweb wiki pages for a partial list), so that your users can browse different versions and different branches of your repository at their leisure.

    In gitweb (and I guess also in other git web interface) thanks to path_info URL support you can view files in browser, and follow links correctly (if they are local), see e.g. git.html from 'html' branch of git.git repository at repo.or.cz.


附言:“git push”默认情况下不会更新远程仓库中的工作目录,因为如果有人在非裸仓库中工作,你推送的这个侧向推送可能会导致意外并且造成工作丢失。

只是为了未来读者注意,这是一篇有用的写作 - 我最终遵循了这个问题的第一个回答,并且真正地使用了这个响应中的第二个选项与我的脚本 - 这是一个很好的选项描述。谢谢! - scottru

0
我使用一个类似这样的 post-receive hook 来发布我的网站,因为在进行推送时,Git不会触及工作目录。远程仓库是一个非裸仓库,也就是说它有一个工作目录。
if [ -n $GIT_DIR ]; then
    # Current dir is "<reporoot>/.git/", but we need to run reset at "<reporoot>/".
    # Clearing GIT_DIR is needed, or reset will fail with "fatal: Not a git repository: '.'"
    unset GIT_DIR
    cd ..
fi
git reset --hard

(顺便说一句,注意我似乎无法推送到“myapp-beta.git” - 这会失败,我必须推送到目录名称。我担心这是问题的一部分,但我不知道我在这里做错了什么。)
创建一个裸的Git存储库(git init --bare),它没有工作目录,惯例是将目录命名为“something.git”。当有一个非裸的存储库时,存储库实际上位于“.git”子目录中,因此完整路径为“something/.git”。看起来在任何情况下,您都可以省略“.git”部分,Git会自动检测到它。

0

我并不是真的反对其他解决方案,但我认为有一种更少hack'ish的“Git方式”来做到这一点。以下是我的做法:

  1. 在我的服务器上,我会设置一种类似于集中式存储库的东西(由Gitosis或某些类似的工具管理)。
  2. 从客户端端,我会不断地从存储库中拉取,进行更改并推回。分支当然是自动管理的。
  3. 我会将所需的分支从Gitosis拉到服务器的public_html / beta_public_html中。我会定期使用Cron作业与Gitosis保持同步。如果您不喜欢Cron作业的想法,您可以像其他人指出的那样使用某种钩子+脚本。

实际上,我通常将我的Git仓库托管(GitHub)与我的Web托管(SliceHost)分开。 - artagnon

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