Git子树合并策略或子树命令? [英] Git subtree merge strategy or subtree command?

查看:150
本文介绍了Git子树合并策略或子树命令?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我开始了一个新的Zend Framework项目,我将与一位设计师合作。我将使用git来维护这个项目代码,通常设计者不会说git(或任何编程语言),所以我想让他们变得轻松,否则恐怕他根本不会使用git。我的计划是给他一些Git gui,并且他应该只使用基本的git特性,例如commit,diff,fetch,merge,push和pull。

m使用gitolite来维护我们的git仓库的共享副本,并且由于它有一个精细的权限系统,所以我会给设计者RW访问权限,只为一个专门的分支(设计)并读取其他分支机构的访问权限。



为了简单起见,我想仅与他分享主项目中的一些文件夹(其中 ZF推荐的结构),为此,他确实需要访问权限。同时,我希望我们两个人仍然可以彼此合并。



他的分支的简化结构应该是这样的:

 <项目名称> / 
应用程序/
布局/
脚本/
views /
脚本/
public /
css /
images /
js /

我知道我可以使用子模块来完成这个任务,但这将会是一件很痛苦的事情,因为我应该在至少4个subrepository中分割我的项目,他应该只能访问subrepositories,他会拥有3存储库的工作。因为这个原因,如果这是我唯一的解决方案,我会放弃这个想法。



我已经阅读的一些链接让我觉得我在问什么是可能的:





以下是我的问题:


  1. 如何创建简化分支 design code>( git checkout -b设计 git mv / rm ?)

  2. 如何配置git来跟踪分支上的编辑(这样我就可以从master分支中 git合并设计,反之亦然)



更新:



我发现了另一种解决这两个问题的可能方法SO问题



我尝试在<$设计分支中的c $ c> git rm all-unneeded-stuff ,我在主分支中进行了一次提交,其中涉及白名单路径中的文件和黑名单路径中的另一个文件,但是 git merge 失败并显示以下消息:

  CONFLICT(删除/修改):应用程序/Bootstrap.php在HEAD中被删除并在master中被修改。应用程序/ Bootstrap.php的版本控制器留在树中。 

然后我在主分支中添加了一个新目录,并且在从设计中合并时添加了新目录。我在驱动程序中添加了一些调试回声,并且我发现这两种情况都没有调用,可能是因为它不是真正的合并。



我没有尝试过第二种方法(.gitignore之一),但如果我理解这种方法不符合我的需求,因为它只会忽略设计分支中列入黑名单的文件,但它们会在设计分支中检出,打破了我的要求。



我在 GitHub

更新2:



我认为目前没有解决方案。对于当前的git实现,这是无法实现的。



我想反驳,但恐怕不会发生。

解决方案

听起来像是希望能够限制每个目录的读取访问权限。这是可能的,但我知道的唯一解决方案远非简单。它涉及服务器上同一个存储库的多个版本,每个版本都使用一些复杂的钩子魔法来过滤出子目录。



我正在执行钩子在我的业余时间,最终的目标是将它们作为开源软件发布(可能作为gitolite的一个附加功能),但不幸的是我的业余时间有限。 $ b

存储库



一般的解决方案至少包含三个版本的同一个版本库:一个 authority 版本库,可协调两个或更多委托库。用户永远不会克隆权威资源库;克隆只有委托仓库。



委托人负责将传入提交转发给权威资源库。授权存储库负责为每个其他代理存储库适当地过滤传入提交。然后将结果推送给其他代表。



权限存储库并非严格要求 - 代理可以自行执行过滤,然后直接将结果推送到其他代表 - 但使用另一个存储库作为集中协调员可以大大简化实施过程。


$ b

委托库



存储库包含整个项目数据的子集(例如,零个或多个子目录被过滤掉)。所有代表存储库彼此完全相同,除了每个代理都有一组不同的文件被过滤掉。它们都具有相同的提交历史记录图,但提交将具有不同的文件内容以及不同的SHA1标识符。它们具有相同的分支和标记集(换句话说,如果项目有一个 master 分支,那么每个委托库也有一个 master 分支),但由于等效提交的SHA1标识符不同,引用将指向不同的SHA1标识符。



例如,以下是两个代表存储库内容的图形。 everything.git 存储库没有任何内容被过滤掉,但是 no-foo.git 存储库包含了所有内容子目录 foo 过滤掉了。

  $ cd〜git / repositories / everything .git 
$ git log --graph --oneline --decorate --date-order --all
* 2faaad9(HEAD,master)barbaz
| * c3eb6a9(发布)foobar
* | 8b56913合并分支'release'
| \\
| | /
| * b8f899c qux
* | aad30f1 baz
| /
* f4acd9f在子目录栏中放入一个新文件
* 2a15586在子目录foo中放入一个新文件

$ cd〜git / repositories / no -foo.git
$ git log --graph --oneline --decorate --date-order --all
* 81c2189(HEAD,master)barbaz
| * 6bbd85f(发布)foobar
* | c579c4b合并分支'release'
| \\
| | /
| * 42c45c7 qux
* | 90ecdc7 baz
| /
* 4d1cd8d在子目录栏中放入一个新文件
* 9cc719d在子目录foo中放入一个新文件


(注意:提交也可以被过滤掉,以防止另一个委托的用户甚至知道已经过滤掉的目录中的提交已经完成。但是,如果提交只能触发已过滤掉的目录中的文件,则只能将其过滤掉;否则,会出现合并冲突,无法通过钩子自动解决。)



权限库



权限库是所有委托权限的超集。每个委托库中的所有提交对象都会通过每个委托库中的钩子自动推送到权限库中。因此,如果有两个代理存储库,那么在代理存储库中将会有两个同构DAG(每个代理一个)(假设代理不共享一个通用根提交)。

权威知识库也将具有每个委托的每个项目分支的版本,并以该委托的名称作为前缀。继续上面的例子, everything.git 委托库有一个 master 分支指向提交 2faaad9 ,而委托 no-foo.git 有一个指向过滤器的 master 分支 - 但是,否则等价提交 81c2189 。在这种情况下, authority.git 会有两个主分支: everything / master 指向 2faaad9 no-foo / master 指向 81c2189 。下图说明了这一点。

  $ cd〜git / repositories / authority.git 
$ git log --graph --oneline --decorate --date-order --all
* 2faaad9(everything / master)barbaz
| * 81c2189(no-foo / master)barbaz
| | * c3eb6a9(一切/发布)foobar
| | | * 6bbd85f(no-foo / release)foobar
* | | | 8b56913合并分支'release'
| \\\\
| | | / /
| | / | |
| | * | c579c4b合并分支'release'
| | | \\
| | | | /
| * | | b8f899c qux
| | | * 42c45c7 qux
* | | | aad30f1 baz
| / / /
| * | 90ecdc7 baz
| | /
* | f4acd9f将一个新文件放在子目录bar
|中* 4d1cd8d将新文件放在子目录bar
* |中2a15586将新文件放在子目录foo中
/
* 9cc719d将新文件放在子目录foo中

请注意,每个提交有两个版本,每个代表一个版本。



钩子



委托仓库



每个委托将提交提交给权威资源库。



当用户更新引用时(通过 git push )在代理仓库中,仓库的 update 钩子会自动在权限仓库中执行 git push 。然而,它并不使用标准的push refspec,而是使用一个refspec,它使得授权仓库中的引用以委托仓库的名称作为前缀(例如,如果委托仓库被命名为 foo.git 然后它将使用像 + refs / heads / master这样的push refspecs:refs / heads / foo / master + refs / tags /v1.0:refs/tags/foo/v1.0 )。



授权库



授权存储库过滤传入的提交并将其压入其他代理存储库。


$ b 当代理存储库推入授权存储库时,授权的 update hook:


  1. 检查用户是否试图在一个文件中创建一个文件的过滤出的目录。如果是这样,它会退出并出现错误(否则可能会出现无法自动解决的合并冲突)。
  2. 移回原先被过滤掉的子目录中,以形成一棵树
  3. 对于每个其他代理,过滤未过滤的树,以便删除相应内容以进行等效提交。 提交给代理仓库。

必须小心避免委托仓库之间的争用情况并妥善处理错误。

您的案例



在您的示例中,您将拥有两个代理存储库:




  • everything.git (for you)

  • zend-project.git (针对您的设计者)



分支位于权限内。 git 会以所有内容为前缀 zend-project 委托存储库。



当您在 everything.git master $ c $>,
$ b


  1. update 钩入 everything.git 会将传入的提交推送到 authority.git 中的 everything / master code>。

  2. 对于每个传入的提交, authority.git 中的更新 code> would:


    1. 创建一个与提交树完全相同的新树对象,但删除应用程序之外的所有内容 code>和 public 子目录。
    2. 使用新树和等价的父级创建新的提交对象,但重用原始提交消息,作者和时间戳。

    3. 更新 zend-project / master 指向新提交。 li>

    4. authority.git中推送 zend-project / master master zend-pr oject.git

    当您的设计师推送至 master $ zend-project.git ,会发生以下情况: zend-project.git 中的 update 钩子会将传入的提交推送到 zend-在code> authority.git


  3. 中的project / master 分支对于每个传入提交, update 钩入 authority.git 会:


    1. 检查是否有新的文件是在应用程序 public 子目录之外创建的。如果是这样,返回一个错误消息。
    2. 创建一个新的树对象,它与提交的树完全相同,除了从 everything / master的其他子目录嫁接进来。

    3. 使用新树和等价父项创建新的提交对象,但重用原始提交消息,作者和时间戳。 li>
    4. 更新 everything / master 以指向新的提交。


  4. authority.git 中的 everything / master 推送至 master in everything.git



注释



以上描述了一种实现按目录读取访问控制的方法。如果您确实不希望某些用户能够访问存储库的某些部分,它应该是合适的。在你的情况下,你的设计师的方便可能比限制访问更重要。如果是这样,可能有一种更简单的方法来实现你想要的。



我希望我能够清楚地解释这一点。


I'm starting a new Zend Framework project in which I will collaborate with a designer. I'm going to maintain this project code using git and usually designers don't speak git (or any programming language) so I wanna make things easy for him, otherwise I'm afraid he won't use git at all. My plan is to give him some Git gui and with that he should use only basic git features such as commit, diff, fetch, merge, push and pull.

I'm using gitolite to maintain the shared copy of our git repository and since it has a granular permission system, I will give the designer RW access only for a dedicated branch (design) and read access to other branches.

To keep things simple I'd like to share with him only some of the folders in the main project (which follows ZF recommended structure) for which he does need access for doing its job. At the same time I want that both of us can still merge from each other.

The reduced structure for his branch should be this:

<project name>/
    application/
        layouts/
            scripts/
        views/
            scripts/
    public/
        css/
        images/
        js/

I know that I could use submodules for this task, but it will be a pain to maintain because I should split my project in (at least) 4 subrepository, he should have access only to subrepositories and he'd have 3 repository to work with. For this reason if this is the only solution I'll give up with this idea.

Some links I've already read that make me think that what I'm asking is possible:

Here are my question:

  1. How to create the reduced branch design (git checkout -b design and git mv/rm?)
  2. How to configure git to keep track of edits across branches (so I can git merge design from the master branch and vice versa)

Update:

I found out another possible approach to the problem given by these two SO questions

I tried to implement the first after git rm all-unneeded-stuff in the design branch, I make a commit in the master branch which involves a file in the whitelisted paths and another file in the blacklisted paths, but git merge fail with the following message

CONFLICT (delete/modify): application/Bootstrap.php deleted in HEAD and modified in master. Version master of application/Bootstrap.php left in tree.

Then I added a new dir in the master branch and when merging from design the new dir is added. I put some debug echo in the driver and I saw that it hasn't be called in both cases, maybe because it's not a real merge.

I haven't tried the second approach (the .gitignore one) yet, but if I've understood the approach doesn't fit my needs because it will only ignore blacklisted files in the design branch, but they will be checked out in the design branch, breaking my requirements.

I pushed my experiments on GitHub

Update 2:

I think that currently there is no solution for that. With the current git implementation this is simply not achievable.

I'd like to be contradicted, but I'm afraid that it won't happen.

解决方案

Sounds like you want to be able to restrict read access on a per-directory basis. This is possible, but the only solution I'm aware of is far from simple. It involves multiple versions of the same repository on your server, each kept in sync using some complicated hook magic to filter out the subdirectories.

I'm working on implementing the hooks in my spare time with the eventual goal of publishing them as open source software (perhaps as a feature addition to gitolite), but unfortunately my spare time is limited.

Repositories

The general solution involves at least three variants of the same repository: One authority repository that coordinates two or more delegate repositories. Users never clone the authority repository; only delegate repositories are cloned.

The delegates are responsible for forwarding incoming commits to the authority repository. The authority repository is responsible for filtering the incoming commits appropriately for each other delegate repository. The results are then pushed down to the other delegates.

The authority repository isn't strictly required—delegates could perform the filtering on their own and then push the results directly to the other delegates—but using another repository as a centralized coordinator simplifies implementation considerably.

Delegate Repositories

Each delegate repository contains a subset of the entire project's data (e.g., zero or more subdirectories filtered out). All delegate repositories are identical to each other except each delegate has a different set of files filtered out. They all have the same commit history graph, but the commits will have different file contents and thus different SHA1 identifiers. They have the same set of branches and tags (in other words, if the project has a master branch, then each delegate repository also has a master branch), but because the SHA1 identifiers for the equivalent commits are different, the references will point to different SHA1 identifiers.

For example, the following are graphs of the contents of two delegate repositories. The everything.git repository doesn't have anything filtered out, but the no-foo.git repository has everything in subdirectory foo filtered out.

$ cd ~git/repositories/everything.git
$ git log --graph --oneline --decorate --date-order --all
* 2faaad9 (HEAD, master) barbaz
| * c3eb6a9 (release) foobar
* |   8b56913 Merge branch 'release'
|\ \  
| |/  
| * b8f899c qux
* | aad30f1 baz
|/  
* f4acd9f put a new file in subdirectory bar
* 2a15586 put a new file in subdirectory foo

$ cd ~git/repositories/no-foo.git
$ git log --graph --oneline --decorate --date-order --all
* 81c2189 (HEAD, master) barbaz
| * 6bbd85f (release) foobar
* |   c579c4b Merge branch 'release'
|\ \  
| |/  
| * 42c45c7 qux
* | 90ecdc7 baz
|/  
* 4d1cd8d put a new file in subdirectory bar
* 9cc719d put a new file in subdirectory foo

Notice that the two graphs look the same, have the same commit messages, the same branch names, etc. The only difference is the SHA1 IDs due to the fact that the file contents are different.

(Side note: Commits can be filtered out as well to prevent users of another delegate from even knowing that a commit in a filtered-out directory was made. However, a commit can only be filtered out if it only touches files in a filtered-out directory. Otherwise, there would be merge conflicts that could not be automatically resolved by the hooks.)

Authority Repository

The authority repository is a superset of all of the delegate authorities. All commit objects in each delegate repository are automatically pushed into the authority repository via a hook in each delegate repository. Thus, if there are two delegate repositories, there will be two isomorphic DAGs (one from each delegate) in the authority repository (assuming the delegates don't share a common root commit).

The authority repository will also have a version of each project branch from each delegate, prefixed by the name of the delegate. Continuing the above example, the everything.git delegate repository has a master branch pointing to commit 2faaad9, while delegate no-foo.git has a master branch pointing to the filtered-but-otherwise-equivalent commit 81c2189. In this scenario, authority.git would have two master branches: everything/master pointing to 2faaad9 and no-foo/master pointing to 81c2189. The following graph illustrates this.

$ cd ~git/repositories/authority.git
$ git log --graph --oneline --decorate --date-order --all
* 2faaad9 (everything/master) barbaz
| * 81c2189 (no-foo/master) barbaz
| | * c3eb6a9 (everything/release) foobar
| | | * 6bbd85f (no-foo/release) foobar
* | | |   8b56913 Merge branch 'release'
|\ \ \ \  
| | |/ /  
| |/| |   
| | * |   c579c4b Merge branch 'release'
| | |\ \  
| | | |/  
| * | | b8f899c qux
| | | * 42c45c7 qux
* | | | aad30f1 baz
|/ / /  
| * | 90ecdc7 baz
| |/  
* | f4acd9f put a new file in subdirectory bar
| * 4d1cd8d put a new file in subdirectory bar
* | 2a15586 put a new file in subdirectory foo
 /  
* 9cc719d put a new file in subdirectory foo

Notice that there are two versions of each commit, one for each delegate. Also notice the branch names.

Hooks

Delegate Repositories

Each delegate feeds commits to the authority repository.

When a user updates a reference (via git push) in a delegate repository, that repository's update hook automatically does a git push into the authority repository. However, instead of using the standard push refspec, it uses a refspec that causes the reference in the authority's repository to be prefixed by the delegate repository's name (e.g., if the delegate repository is named foo.git then it will use push refspecs like +refs/heads/master:refs/heads/foo/master and +refs/tags/v1.0:refs/tags/foo/v1.0).

Authority Repository

The authority repository filters incoming commits and pushes them down into the other delegate repositories.

When a delegate repository pushes into the authority repository, the authority's update hook:

  1. Checks to see if the user is trying to create a file in one of the filtered-out directories. If so, it exits with an error (otherwise there could be merge conflicts which can't be resolved automatically).
  2. Grafts back in the subdirectories that were originally filtered out to form a tree that has nothing filtered out.
  3. For each other delegate, filter the unfiltered tree to make an equivalent commit with the appropriate contents removed.
  4. Push the equivalent commits to the delegate repositories.

Care must be taken to avoid race conditions between delegate repositories and to properly handle errors.

Your Case

In your example, you would have two delegate repositories like this:

  • everything.git (for you)
  • zend-project.git (for your designer)

Branches in authority.git would be prefixed by everything and zend-project corresponding to the two delegate repositories.

When you push to master in everything.git, the following would happen:

  1. The update hook in everything.git would push the incoming commits to the everything/master branch in authority.git.
  2. For each incoming commit, the update hook in authority.git would:

    1. Create a new tree object that is 100% identical to the commit's tree but remove everything outside of the application and public subdirectories.
    2. Create a new commit object using the new tree and equivalent parent(s), but reuse the original commit message, author, and timestamp.
    3. Update zend-project/master to point to the new commit.

  3. Push zend-project/master in authority.git to master in zend-project.git.

When your designer pushes to master in zend-project.git, the following would happen:

  1. The update hook in zend-project.git would push the incoming commits to the zend-project/master branch in authority.git.
  2. For each incoming commit, the update hook in authority.git would:

    1. Check to see if any new files were created outside the application or public subdirectories. If so, return with an error message.
    2. Create a new tree object that is 100% identical to the commit's tree except with the other subdirectories from everything/master grafted in.
    3. Create a new commit object using the new tree and equivalent parent(s), but reuse the original commit message, author, and timestamp.
    4. Update everything/master to point to the new commit.

  3. Push everything/master in authority.git to master in everything.git.

Notes

The above describes a way to implement per-directory read access control. It should be suitable if you really don't want certain users to be able to access parts of the repository. In your case, convenience for your designer may be more important than limiting access. If so, there may be a simpler way to accomplish what you want.

I hope I was able to explain this clearly enough.

这篇关于Git子树合并策略或子树命令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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