切换到特定分支 [英] Switching remote to a specific branch

查看:123
本文介绍了切换到特定分支的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我明白分支的价值,但有一件事让我感到困惑。我有我的本地回购。我将更改推送到远程,该远程具有一个后置接收挂钩,用于在网站上写入文件。所以我创建了一个分支(新分支)来尝试一些东西。我编辑文件,提交并推送到远程。大。问题是,虽然我的遥控器上的回购是最新的,但我认为它仍然设置为主,因为新分支中的更改未反映在网站上。如何将遥控器设置为特定的分支,以便分支机构驱动后接收钩子,而不仅仅是向主分支默认?我只是在主人合并,但我还没有准备好,因为我仍然在工作分支上扭结。

好吧,根据评论,这听起来像你控制服务器,这是需要在这里。 (也就是说,我在下面写下他们,这只是你自己,而是戴着不同的帽子)。

code> git push 和裸存储库

从git的角度来看,你在处理你的repo,他们)在他们的回购工作。所以当你做一个 git push 时,它会直接提交你的repo提交,他们可能正在处理他们的repo。他们可能已经检出了一些分支,正在编辑他们的 xyz.html 或其他任何副本。



如果您的推送覆盖了他们的 xyz.html ,是不是会惹恼他们?他们正在编辑中!因此,对于一个传统的仓库,git会拒绝尝试推送到当前检出的任何分支(一个 git status 打印为#on branch ... ,或者显示为 git symbolic-ref --short HEAD )。 1 您只能推送到其他分支,根据定义,它们并没有工作,所以它们不会被惹恼。



现在,其实很多这些集中的push here存储库<不要有任何人在他们身上工作。它们通常被设置为裸存储库,这意味着它们根本没有工作树。这反过来意味着他们 - 不管他们是谁 - 不可能在他们的 xyz.html 的副本上工作,因为他们没有 首先是 xyz.html 的工作副本。使用 - bare 克隆,或者将常规仓库转换为裸仓库,禁用无法推送到当前分支检查,这正是他们想要的。 / p>




1 特别是,你会得到一个拒绝更新签出分支的错误。这实际上可以通过 receive.denyCurrentBranch 在服务器的(非裸)存储库中配置。






自动部署



目前为止这么好;但现在他们可能希望他们的中央推送服务器裸回购也自动部署到一个网站(或者代码库,像Jenkins这样的测试系统,或者其他) - 关键是他们,无论他们是谁,都有一个接收后的钩子来部署一个特定的分支。但是我们只是将裸仓库描述为没有工作树。这个自动部署如何工作?让我们穿上我们的另一顶帽子,成为他们。

因为这是git,实际上有很多方法,但通常的(也许是最好的)方法是使用post-receive脚本来检查哪个分支正在更新,并且如果有兴趣的分支被推送到,那么部署就完成了。



下面是一个简单的 post-receive 钩子shell脚本的例子,它有一个未实现的 deploy shell函数来部署推送to master

 #! / bin / sh 
$ b deploy()
{
local newref = $ 1 branch = $ 2

echo部署:$ newref $ branch
}

while read oldsha newsha refname;在
refs / heads / master中做
的情况$ refname)部署$ newsha $ {refname#refs / heads /} ;;
esac
完成

Git使用stdin运行post-receive钩子喂了一系列输入线。每行有三个项目。最后一个是引用的全名,所以分支主机是 refs / heads / master 。前两个分别是旧SHA-1和新SHA-1,以供参考。



旧的和新的SHA- 1的值可以是40 0 字符:这意味着正在创建引用(old-SHA-1全为0)或删除(new全为零)。否则,引用当前存在并且只是被更新(通常指向一个新的提交):它用于指向旧的ID,现在指向新的。



(在 pre -receive hook中,它可以得到完全相同的东西,你可以拒绝更新引用的尝试。在 post - 接收钩子中已经有更新发生了,唯一可以做的就是报告它或以某种方式利用它。)



在我们的案例中,我们真的不关心旧的价值。分支 master 之前从未存在过,或者之后部署的都不重要。 (好吧,我们可能需要它,在这种情况下,我们可以添加它:毕竟这只是一个shell脚本)。我们甚至不需要新的值,因为我们可以读取使用git命令直接从存储库中取出,但是很高兴拥有它,特别是如果我们想检查并投诉分支已被删除。 (如果尝试读取 master 分支引用失败,那么该分支可能被删除,但如果服务器着火并且存储库虽然在这种情况下,我们可能不会在意:-))



然而,主要的是,我们需要做的是检查所有分支 master 中的文件将它们粘贴到部署区域中。事实证明,我们可以通过简单的 git checkout 来做到这一点,即使是在裸仓库中,通过指定一个备用工作树:

  NULL_SHA1 = 0000000000000000000000000000000000000000#40 0s 

deploy(){
local newref = $ 1 branch = $ 2

if [$ newref = $ NULL_SHA1];那么
echodeploy:branch $ branch is deleted!
返回1
fi
#接下来的位很笨,硬编码为master,即使
#我们有$ branch,但是读到...
git - 注意事项:--work-tree = / deploy / master checkout master
}



<有时候你会看到 GIT_WORK_TREE = / deploy / master git checkout master 或类似的东西,如果你不指定 --work-tree git使用 $ GIT_WORK_TREE 如果设置的话。)



我们不仅需要一个( master )自动部署分支,我们可以向调用 deploy 在我们的脚本中,并修复这个愚蠢的位:检查 $ branch / deploy / $ branch ,for实例。



这里有一些潜在的故障:


  1. 目标目录(这里 / deploy / master )必须存在。

  2. 这种 git checkout 更新g它是当前分支的概念。裸存储库仍然有这样的事情:仍有一个名为 HEAD 的文件,其中包含当前分支名称。如果我们 git checkout otherbranch ,我们会改变它;这会影响从这个仓库克隆的 git clone 操作。

  3. Git会确保我们不会破坏已修改的文件。根据部署目录和在那里完成的工作,这可能不是问题。

  4. Git喜欢优化签出。

第1项很容易通过首先执行 mkdir -p 来修复。



如果我们只部署 master ,或者如果我们不介意可能检出分支 otherbranch 自动。但是我们可以通过使用不同形式的 git checkout 来修复它。我们可以添加 -f 来修复第3项。

  git --work-tree = / deploy / $分支签出-f $分支 - 。 

然而,这引出了一个新问题,与第4项一致。 b
$ b

第4项是最棘手的。当你在最后没有结帐时 - 。,以便切换分支,git更新其索引(暂存区域)文件来跟踪工作树中已经存在的内容。然后当你做一个新的 git checkout 来代替前一个时,它可以指出哪些文件可以单独保留,哪些文件必须被删除,哪些必须删除重写或添加。一些部署脚本(签出一个分支,比如 master )取决于这种行为。



如果我们切换部署代码使用 - 。表单,git 仍然更新索引,但不会删除文件为了我们。这意味着我们需要清理掉消失的文件。 (它也造成了一个非常混乱的索引,但通常这没问题:这是一个 - 裸存储库,无论如何都没有人工作。)



清理正确的文件非常棘手。我们可以使用 $ oldsha 方法,比较旧的和新的提交来确定要删除的文件。或者我们可以简单地将 / deploy / $分支目录完全删除:

  rm -rf / deploy / $ branch 
mkdir / deploy / $ branch
git --work-tree = / deploy / $分支签出-f $分支 - 。

这通常就足够了。它仍然存在一个错误:当部署版本被删除时,很短的时间内,当部署目录中的事情变得混乱时,新的部署版本正在建立。但有些时候,这很好。



我们可以通过切换顺序来修复bug:创建一个新的空目录,填充它,然后 mv 新目录到位( mv 旧版本),然后只有 rm -rf (移出的)旧的。仍然有一个小窗口,在这个窗口中deploy目录不存在,但它尽可能小。 (嗯,有可能完全用符号链接关闭它:请参阅下面的注释。)

我们可以做的最后一个技巧是我从未真正测试过的:我们可以通过为每个部署目录使用不同的索引文件,让git完成大部分工作。如果设置了Git,则使用 $ GIT_INDEX_FILE ;如果不是,则使用 $ GIT_DIR / index 。因此,如果我们要设置 GIT_INDEX_FILE = $ GIT_DIR / index。$ branch ,我们应该得到一个特定分支名称的文件。然后,我们可以返回到 git checkout 的检出特定分支表单,并根据需要删除文件。



最后一种方法会将不一致性窗口扩大一点:如果git必须删除一打文件并更新或创建另外100个文件,则使用部署版本的任何人都有更多的机会将部分删除和/或更新视为 git checkout 进度。但它更加简单,并且在工作树中没有浪费的运动。



最后一个讽刺性的沉思,因为它是



请注意,我们的 deploy 函数会设置一个工作树 - 或者可能是其中一个,如果我们有多个可部署分支的话 - 作为一个文字git工作树,甚至可能使用 $ GIT_WORK_TREE 。然后它会在该工作树中肆意渲染任何东西,并将其替换为有趣分支中的最新版本。这正是我们所说的会令人讨厌的,回到背景部分。它根本不烦人,这正是我们想要的!



好的,有点。关于这个工作树的好处,与裸存储库相比,你可以不小心 cd / deploy / master ,看到一个 .git 目录,并开始在其中工作。这里 不存在 .git 目录。尽管如此,这里的工作目录和那里的裸仓库的组合几乎在任何意义上都与常规,非裸仓库等同。


So I understand the value of branching, but one thing is confusing me. I have my local repo. I push changes to a remote, which has a post receive hook set up to write files on a website. So I create a branch (new-branch) to try out something. I edit files, commit, and push to the remote. Great. Problem is, while the repo on my remote is up to date, I guess it's still set to master, as the changes in new-branch aren't reflected on the site. How do I set the remote to a particular branch so that branch is driving the post receive hook, and not just defaulting to the master branch? I would just merge on master but I'm not ready to as I'm still working out the kinks on the branch.

解决方案

OK, based on comments, it sounds like you control the server too, which is needed here. (That is, where I write "they" below, it's just you yourself, but wearing a different hat, as it were.)

Let's start with some background: git push and "bare" repositories

From git's point of view, you work on your repo, and they (whoever they are) work on their repo. So when you do a git push, which delivers commits you've made directly to their repo, they're probably working on their repo. They probably have some branch checked out and are in the middle of editing their copy of xyz.html or whatever.

If your push overwrote their xyz.html, wouldn't that annoy them? They're in the middle of editing! So, for a conventional repository, git will reject an attempt to push to whatever branch is currently checked-out (the one git status prints as "# on branch ...", or shown by git symbolic-ref --short HEAD).1 You can only push to other branches, which by definition, they're not working on, so they won't get annoyed.

Now, in fact, a lot of these centralized "push here" repositories don't have anyone working on them. They're often set up as "bare" repositories, which means they have no work-tree at all. This in turn means the "they"—whoever "they" may be—cannot possibly be working on their copy of xyz.html, since they don't have a working copy of xyz.html in the first place. Cloning with --bare, or converting a regular repository to a bare one, disables the "can't push to current branch" check, which is what they want here.


1In particular, you get a "refusing to update checked out branch" error. This is actually configurable, via receive.denyCurrentBranch, in the (non-bare) repository on the server.


Auto-deploy

So far so good; but now they may want their central push-server bare repo to also automatically deploy to a web site (or, for code repositories, to a test system like Jenkins, or whatever)—the key point is that "they", whoever they are again, have a post-receive hook that deploys one particular branch. But we just described the bare repository as having no work-tree. How does this auto-deploy work? Let's put on our other hat and become "them".

Because this is git, there are actually lots of ways, but the usual (and maybe-best) method is with a post-receive script that checks which branch(es) are being updated, and if the interesting one(s) is/are pushed-to, does the deployment.

Here's an example of a ridiculously simple post-receive hook shell script that has an unimplemented deploy shell function to deploy pushes to master:

#! /bin/sh

deploy()
{
    local newref=$1 branch=$2

    echo deploy invoked: $newref $branch
}

while read oldsha newsha refname; do
    case "$refname" in
    refs/heads/master) deploy $newsha ${refname#refs/heads/};;
    esac
done

Git runs the post-receive hook with its stdin being fed a series of input lines. Each line has three items. The last one is the full name of the reference, so branch master is refs/heads/master. The first two are the "old" SHA-1 and the "new" SHA-1 respectively, for that reference.

At most one of the old and new SHA-1 values can be 40 0 characters: this means the reference is being created (old-SHA-1 is all zeros) or deleted (new is all zeros). Otherwise, the reference currently exists and is simply being updated (pointed to a new commit, normally): it used to point to the old ID and now points to the new.

(In a pre-receive hook, which gets exactly the same stuff, you can reject the attempt to update the reference. In a post-receive hook the update has already happened and the only thing you can do is report on it or make use of it somehow.)

In our case, we really don't care about the old value. It doesn't matter if branch master never existed before, or what was deployed then. (Well, we might want it, in which case we could add it: this is just a shell script, after all.) We don't even really need the new value, because we can read that right out of the repository with a git command, but it's nice to have it, especially if we want to check and complain if the branch has been deleted. (The branch is probably deleted if the attempt to read the master branch reference fails, but that could happen if the server has caught fire and the repository is half-destroyed, too. Although in this case we might not care any more. :-) )

Mainly, though, what we need to do is check out all the files from branch master, sticking them into a deployment area. As it turns out, we can do this with a simple git checkout, even in a bare repository, by specifying an alternate work-tree:

NULL_SHA1=0000000000000000000000000000000000000000 # 40 0s

deploy() {
    local newref=$1 branch=$2

    if [ $newref = $NULL_SHA1 ]; then
        echo "deploy: branch $branch is deleted!"
        return 1
    fi
    # next bit is stupid, hardcodes "master" even though
    # we have "$branch", but read on...
    git --work-tree=/deploy/master checkout master
}

(Side note: sometimes you will see this as GIT_WORK_TREE=/deploy/master git checkout master or similar. That does exactly the same thing. If you don't specify --work-tree git uses $GIT_WORK_TREE if it's set.)

If we want more than just one (master) auto-deploy branch, we can add more branch names to the set that invoke deploy in our script, and fix the dumb bit: check out $branch to /deploy/$branch, for instance.

There are a number of potential glitches here though:

  1. The target directory (here /deploy/master) must exist.
  2. This kind of git checkout updates git's notion of "current branch". A bare repository still has such a thing: there's still a file named HEAD that contains the current branch name. If we git checkout otherbranch we'll change it; and this affects git clone operations that clone from this bare repository.
  3. Git will make sure we don't clobber modified files. Depending on the deployment directory and what gets done there, this may not be an issue.
  4. Git likes to optimize checkouts.

Item 1 is easy enough to fix by first doing a mkdir -p.

Item 2 is not a problem if we only deploy master, or if we don't mind new clones potentially checking out branch otherbranch automatically. But we can fix it by using a different form of git checkout. We can add -f as well to fix item 3.

git --work-tree=/deploy/$branch checkout -f $branch -- .

This all introduces a new problem, though, which goes along with item 4.

Item 4 is the trickiest. When you do a checkout without the -- . at the end, so that you're switching branches, git updates its index (staging area) file to keep track of what's already in the work tree. Then when you do a new git checkout that replaces the previous one, it can tell which files can be left alone, which ones must be removed if any, and which ones must be rewritten or added. Some deployment scripts (that check out one branch, like master) depend on this behavior.

If we switch the deployment code to use the -- . form, git still updates the index, but it won't remove files for us. This means we need to clean out files that went away. (It also winds up making a very messy index, but usually that's OK: this is a --bare repository that no one works in anyway.)

Cleaning out just the right files is tricky. We can use the $oldsha method, comparing the old and new commits to determine what files to remove. Or we can simply blow away the /deploy/$branch directory entirely:

rm -rf /deploy/$branch
mkdir /deploy/$branch
git --work-tree=/deploy/$branch checkout -f $branch -- .

This is often sufficient. It still has a bug: there's a short period when the old deployed version is being removed, and the new deployed version is being built up, when things are a mess in the deploy directory. But sometimes that's OK.

We can mostly-fix the bug by switching the order around: make a new empty directory, populate it, then mv the new directory into place (mving the old one out of the way) and only then rm -rf the (moved-out-of-the-way) old one. There's still a tiny window during which the deploy directory does not exist, but it's as small as possible. (Well, there's a potential to close it entirely with symlinks: see comments below.)

There's one last trick we can do, which I have never actually tested: we can get git to do most of the work, by using a different index file for each deploy directory. Git uses $GIT_INDEX_FILE, if it's set, or $GIT_DIR/index if not. So if we were to set GIT_INDEX_FILE=$GIT_DIR/index.$branch, we should get a file unique to the particular branch name. Then we can go back to the "check out a specific branch" form of git checkout and let it remove files if needed.

This last method opens the inconsistency window a bit wider: if git has to remove a dozen files and update or create another 100 files, whatever's using the deployed version has a lot more chances to see partial removes and/or updates as git checkout progresses. But it is a lot simpler and has less "wasted motion" in the work tree.

One final ironic musing, as it were

Note that our deploy function sets up a work tree—or maybe one of several, if we have multiple deployable branches—as a literal git work tree, possibly even using $GIT_WORK_TREE. It then clobbers whatever is in that work tree, replacing it with the latest version in the interesting branch. This is exactly what we said would be annoying, back in the "background" section. It's not annoying at all, it's just what we want!

Well, sort of. The nice thing about this work tree, vs that bare repository, is that you cannot accidentally cd /deploy/master, see a .git directory, and start working in it. There is no .git directory here. Nonetheless, the combination of "work directory here" plus "bare repository there" really just equals "regular, non-bare repository", in almost every sense.

这篇关于切换到特定分支的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆