简体   繁体   English

Git接收后挂钩不起作用?

[英]Git post-receive hook not working?

I'm trying to set up a Git repo so that whenever I push to master on GitHub from my local machine, the new changes are automatically deployed on a remote server. 我正在尝试设置一个Git存储库,以便每当我从本地计算机推送到GitHub上的master时,新更改都会自动部署在远程服务器上。 But I think I'm missing something fundamental about how hooks work. 但是我认为我缺少有关钩子如何工作的基本知识。

I've set up a .git/hooks/post-receive script on the remote server that looks like this: 我在远程服务器上设置了一个.git/hooks/post-receive脚本,如下所示:

#!/bin/sh

GIT_WORK_TREE=/home/me/webapps/myapp git checkout -f master
GIT_WORK_TREE=/home/me/webapps/myapp git reset --hard

If I run it, it does this: 如果我运行它,它将执行以下操作:

$ /home/me/webapps/myapp/.git/hooks/post-receive
Already on 'master'
Your branch is up-to-date with 'origin/master'.
HEAD is now at 527755e Initial commit

However, 527755e is not the latest head available on GitHub - I've pushed more changes since then. 但是, 527755e并不是GitHub上可用的最新头-从那时起,我进行了更多更改。 It's the head on the remote machine. 它是远程计算机上的头。

I think I must be missing something. 我想我一定很想念东西。 How does the hook on the remote server "know" when I push to master? 当我推送到主服务器时,远程服务器上的钩子如何“知道”?

How do I need to change this so that pushes from local are automatically copied to the remote server? 我需要如何更改此设置,以便将本地推送自动复制到远程服务器?

I think I must be missing something. 我想我一定很想念东西。 How does the hook on the remote server "know" when I push to master? 当我推送到主服务器时,远程服务器上的钩子如何“知道”?

It doesn't—but if set up correctly, it may not have to. 并非如此,但如果设置正确,则可能不必这样做。 "Correctly" depends on various conditions. “正确”取决于各种条件。

Remember, each Git repository is independent of any other Git repository and controls its own destiny. 请记住,每个Git存储库都独立于任何其他Git存储库,并控制自己的命运。 It may help to give each repository a name: A, B, and C; 给每个存储库起一个名字可能会有所帮助:A,B和C; or Alice, Bob, and Carol; 或爱丽丝,鲍勃和卡罗尔; or whatever. 管他呢。 Here, let's suppose there are exactly two repositories: yours, which is repo R, and the server's, which is repo S. 在这里,让我们假设确实有两个存储库:您的存储库,是存储库R,和服务器的存储库,是存储库S。

There are things that you do on repo R that affect R. These include: 在回购R上执行的某些操作会影响R。这些操作包括:

  • checking out specific commits, perhaps by branch name; 检查特定的提交,也许按分支名称;
  • adding new commits; 添加新的提交;
  • moving branch names from one commit to another; 将分支名称从一个提交移动到另一个;
  • running git fetch . 运行git fetch

The last one of these has your Git call up another Git, such as the one at S, and collect commits from it. 其中的最后一个让您的Git调用另一个Git,例如位于S的那个,并从中收集提交。 After collecting commits, your Git alters your Git's remote-tracking names such as origin/master . 收集提交后,您的Git会更改您的 Git的远程跟踪名称,例如origin/master This has no effect on your own branch names, which are not in this remote-tracking name space and do not start with origin/ . 这对您自己的分支名称没有影响,这些分支名称不在此远程跟踪名称空间中,也不以origin/开头。

Someone on the server, in control of repo S, can do the same sorts of things over there, but typically no one actually works on the server. 服务器上的某人可以控制repo S,在那里做同样的事情,但是通常没有人在服务器上实际工作 In fact, the repository on S is typically a bare repository specifically so that no one on S can do any work on it. 实际上,S上的存储库通常专门是一个存储库, 因此 S 没有人可以对其进行任何工作。 It's this lack-of-work that specifically makes it possible for S to be a target of git push (modulo the newfangled updateInstead stuff mentioned below). 正是这种缺乏工作,才使S成为git push目标 (对下面提到的newfangled updateInstead模)。

When Git is a target of git push , it: 当Git git push的目标时,它将:

  • receives some objects, typically commits, from some other repository; 从其他存储库接收一些对象,通常是提交; then 然后
  • receives some name-update requests or commands from whoever is sending those commits. 从发送这些提交的任何人接收一些名称更新请求或命令。

Note that these name updates are not moved into some separate name-space. 请注意,这些名称更新不会移到某些单独的名称空间中。 This is very different from git fetch ; 这与git fetch有很大的不同; we'll come back to this in a moment. 我们待会儿再讲这个。

The receiving Git on S does all this using git receive-pack , which is not a command anyone would normally run themselves. S上的接收Git使用git receive-pack来完成所有这一切,这不是任何人通常自己运行的命令。 The receive-pack command, though, runs a bunch of hooks. 但是,receive-pack命令会运行很多钩子。 The current complete list is: 当前的完整列表是:

  • pre-receive: once, before starting any name updates; 预先接收:一次,开始任何名称更新之前;
  • update: once per name-update; 更新:每个名称更新一次;
  • post-receive: once, after doing all name updates; 后接收:一次,在完成所有名称更新后;
  • post-update: once, after doing all name updates; 更新后:一次,在完成所有名称更新后;
  • push-to-checkout: once, if and when pushing to the current branch of a non-bare repository in which the configuration key receive.denyCurrentBranch is set to updateInstead . push-to-checkout:一次,是否以及何时推送到非裸存储库的当前分支,在该分支中,配置密钥receive.denyCurrentBranch设置为updateInstead

but some of these are new in newer versions of Git. 但是其中一些是较新版本的Git中的新功能。 The standard three available even in very old versions of Git are pre-receive, update, and post-receive. 即使在非常老的Git版本中也可以使用的标准三个是接收前,更新和接收后。 (For more on what each hook can do, consult the githooks documentation , preferably the documentation for your particular installation since these have changed over time.) (有关每个钩子可以做什么的更多信息,请查阅githooks文档 ,最好是您特定安装的文档,因为它们随着时间的推移而发生了变化。)

So what does all this mean in the end? 那么这一切到底意味着什么?

Remember, whoever is doing the git push generally requests that the server set repo S's branches . 请记住,执行git push通常会要求服务器设置存储库S的branchs Hence, after S gets and accepts a request to change its own master , the name master in repository S means the commit we just received and accepted . 因此,S获得并接受改变自己的请求后master ,名master存储库S表示的承诺,我们刚刚收到并接受

Since S runs these hooks before, during, and just after receiving and executing the name-update requests, we can have S run: 由于S在接收和执行名称更新请求之前,之中和之后运行这些钩子,因此我们可以运行S:

git --work-tree=<path> checkout <args>

The --work-tree=<path> , or its equivalent using the GIT_WORK_TREE environment variable, overrides the "bare"-ness of the bare repository S so that S becomes a non-bare repository with a work-tree. --work-tree=<path>或等效于使用GIT_WORK_TREE环境变量的等效文件,将覆盖裸存储库S的“裸”性质,以便S成为具有工作树的非裸存储库。 Note that S's index, which indexes this (single) work-tree, remains associated with S itself (it's in the Git directory, as the file named index ): there is only one default index, so it can only index one work-tree. 请注意,索引该(单个)工作树的S索引仍然与S本身关联(它位于Git目录中,作为名为index的文件):只有一个默认索引,因此它只能索引一个工作树。 。 If some branch name(s) are updated, any git checkout <branch-name> may now check out some other commit, different from the one previously checked-out. 如果某些分支名称已更新,则任何git checkout <branch-name>现在都可以签出其他一些提交,这与以前签出的提交不同。

There is an obvious potential race condition here: what if two or more users on some repositories Q and R run git push to S simultaneously , asking S to change its master from, say, aaaaaaa... to bbbbbbb... (Q) and ccccccc... (R)? 这里有一个明显的潜在竞争条件:如果某些存储库Q和R上的两个或更多用户同时运行git pushS ,要求S将其masteraaaaaaa...更改为bbbbbbb... (Q),该aaaaaaa... bbbbbbb...ccccccc... (R)? For the name update itself, one of the two will "win" the race and set the name-to-hash mapping first. 对于名称更新本身,两者之一将“赢得”比赛并首先设置名称到哈希的映射。 The other will see that the name master is locked and be told to back off try again. 另一个将看到名称master已锁定,并被告知退回重试。 So the true "simultaneous" case is OK. 因此,真正的“同时”情况是可以的。 However, we can also have one succeed in updating master and then start the post-receive hook. 但是,我们也可以成功更新master ,然后启动post-receive挂钩。 The other one can then come along and also update master . 然后,其他人可以一起去, 更新master The first post-receive hook might see either version of master , and the second post-receive hook might begin running while the first is still running. 第一个接收后挂钩可能会看到master任一版本,而第二个接收后挂钩可能会在第一个仍在运行时开始运行。

There appears to be no code in modern Git to prevent this. 现代Git中似乎没有代码可以阻止这种情况。 Older versions of Git left lock files around during much of the operation that prevented a second receive-pack from starting, but I think even those unlocked the repository before running the post-receive hook. 较早版本的Git在很多操作期间都留下了锁定文件,阻止了第二个接收包的启动,但是我认为即使是那些在运行后接收挂钩之前也已解锁存储库的文件。 If you want true atomicity and are doing something complicated, it might be wise to implement your own locking. 如果您想要真正的原子性并且正在做复杂的事情,那么实现自己的锁定可能是明智的。

For git checkout , though, this should mostly be harmless: the one that "wins the race" will either check out the old master or the new master —it literally cannot see any other value for the reference—and then the race-losing one, assuming that its push is not rejected as a non-fast-forward in the first place, will check out the new master . 不过,对于git checkout来说,这基本上应该是无害的:“赢得比赛”的人要么检出旧master还是新master字面上看不到其他任何参考值),然后是输掉比赛的那个,假设一开始它的推送未被拒绝为非快进,则将签出新的master The checkout process itself holds a lock on the index file while the work-tree is updated, since the index is cacheing data from the work-tree. 在更新工作树时,检出过程本身会在索引文件上保持锁定,因为索引正在缓存来自工作树的数据。

So this assumes that the post-receive hook is on the server 所以这个假设后收到钩服务器上

Now we can see what "set up correctly" means: the post-receive hook must run on the (single) server that you have designated as the server. 现在我们可以看到什么是“正确设置”是指:后收到钩必须是已指定为服务器 (单)服务器上运行。

If there is more than one server—for instance, if you use GitHub as the central repository, but the web server is elsewhere—then you are stuck: there is no way to know, on the web server, that the GitHub server was just updated. 如果有一台以上的服务器(例如,如果您使用GitHub作为中央存储库,而Web服务器在其他位置),那么您将陷入困境:无法在Web服务器上知道GitHub服务器只是更新。

Fortunately, GitHub provides "notification" hooks: you can set things up so that your web server knows to fetch from the GitHub server, once it receives such a notification. 幸运的是,GitHub提供了“ notification”挂钩:您可以进行设置,以便您的Web服务器在收到此类通知后便知道要从GitHub服务器获取 However, this requires a completely different setup on the web server, not a post-receve hook. 但是,这需要在Web服务器上进行完全不同的设置,而不是接收后挂钩。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM