你可以使用Subversion进行部分检出吗?

177

如果我在trunk/下有20个目录,每个目录都有很多文件,但是我只需要其中的3个目录,那么是否可能只通过svn checkout检出这三个目录呢?


参见:https://dev59.com/0XI-5IYBdhLWcg3wy72n?lq=1 - Nakilon
8个回答

296

多亏了这里的评论,看起来稀疏目录是正确的方式。我相信以下代码应该可以实现:

svn checkout --depth empty http://svnserver/trunk/proj
svn update --set-depth infinity proj/foo
svn update --set-depth infinity proj/bar
svn update --set-depth infinity proj/baz

或者,使用--depth immediates而不是empty来检出位于trunk/proj中的文件和目录但不包括它们的内容。这样,您就可以看到存储库中存在哪些目录。


正如@zigdon的回答中提到的那样,您还可以执行非递归检出。这是一种旧的、不太灵活的方法,可以实现类似的效果:

svn checkout --non-recursive http://svnserver/trunk/proj
svn update trunk/foo
svn update trunk/bar
svn update trunk/baz

4
如果我在主干目录上执行 svn update,它会拉取所有其他文件夹,还是只会更新已经获取的文件夹? - Rob Walker
2
在执行 svn update --set-depth infinity proj/foo 后,我收到了 Skipped 'prom/foo' 的提示:( - sam
2
哦,你必须先更新父级目录(proj/foo),然后才能更新更深的目录(proj/foo/boo)。 - sam
4
这是一个很好的答案,实际上应该标为正确的答案。谢谢pkaeding! - Jimbo
1
你可能需要使用一个中间步骤 svn update --set-depth immediates proj,这样它就会创建 proj/foo 以进行更新。 - Craig
显示剩余6条评论

82

Subversion 1.5 推出了稀疏检出(sparse checkouts)功能,这可能是你会觉得有用的东西。根据文档

...稀疏目录(或浅层检出)...允许你以比完全递归更浅的方式轻松地检出工作副本或部分工作副本,并自由地在稍后时间引入先前被忽略的文件和子目录。


8
我写了一个脚本来自动化复杂的稀疏检出。
#!/usr/bin/env python

'''
This script makes a sparse checkout of an SVN tree in the current working directory.

Given a list of paths in an SVN repository, it will:
1. Checkout the common root directory
2. Update with depth=empty for intermediate directories
3. Update with depth=infinity for the leaf directories
'''

import os
import getpass
import pysvn

__author__ = "Karl Ostmo"
__date__ = "July 13, 2011"

# =============================================================================

# XXX The os.path.commonprefix() function does not behave as expected!
# See here: http://mail.python.org/pipermail/python-dev/2002-December/030947.html
# and here: http://nedbatchelder.com/blog/201003/whats_the_point_of_ospathcommonprefix.html
# and here (what ever happened?): http://bugs.python.org/issue400788
from itertools import takewhile
def allnamesequal(name):
    return all(n==name[0] for n in name[1:])

def commonprefix(paths, sep='/'):
    bydirectorylevels = zip(*[p.split(sep) for p in paths])
    return sep.join(x[0] for x in takewhile(allnamesequal, bydirectorylevels))

# =============================================================================
def getSvnClient(options):

    password = options.svn_password
    if not password:
        password = getpass.getpass('Enter SVN password for user "%s": ' % options.svn_username)

    client = pysvn.Client()
    client.callback_get_login = lambda realm, username, may_save: (True, options.svn_username, password, True)
    return client

# =============================================================================
def sparse_update_with_feedback(client, new_update_path):
    revision_list = client.update(new_update_path, depth=pysvn.depth.empty)

# =============================================================================
def sparse_checkout(options, client, repo_url, sparse_path, local_checkout_root):

    path_segments = sparse_path.split(os.sep)
    path_segments.reverse()

    # Update the middle path segments
    new_update_path = local_checkout_root
    while len(path_segments) > 1:
        path_segment = path_segments.pop()
        new_update_path = os.path.join(new_update_path, path_segment)
        sparse_update_with_feedback(client, new_update_path)
        if options.verbose:
            print "Added internal node:", path_segment

    # Update the leaf path segment, fully-recursive
    leaf_segment = path_segments.pop()
    new_update_path = os.path.join(new_update_path, leaf_segment)

    if options.verbose:
        print "Will now update with 'recursive':", new_update_path
    update_revision_list = client.update(new_update_path)

    if options.verbose:
        for revision in update_revision_list:
            print "- Finished updating %s to revision: %d" % (new_update_path, revision.number)

# =============================================================================
def group_sparse_checkout(options, client, repo_url, sparse_path_list, local_checkout_root):

    if not sparse_path_list:
        print "Nothing to do!"
        return

    checkout_path = None
    if len(sparse_path_list) > 1:
        checkout_path = commonprefix(sparse_path_list)
    else:
        checkout_path = sparse_path_list[0].split(os.sep)[0]



    root_checkout_url = os.path.join(repo_url, checkout_path).replace("\\", "/")
    revision = client.checkout(root_checkout_url, local_checkout_root, depth=pysvn.depth.empty)

    checkout_path_segments = checkout_path.split(os.sep)
    for sparse_path in sparse_path_list:

        # Remove the leading path segments
        path_segments = sparse_path.split(os.sep)
        start_segment_index = 0
        for i, segment in enumerate(checkout_path_segments):
            if segment == path_segments[i]:
                start_segment_index += 1
            else:
                break

        pruned_path = os.sep.join(path_segments[start_segment_index:])
        sparse_checkout(options, client, repo_url, pruned_path, local_checkout_root)

# =============================================================================
if __name__ == "__main__":

    from optparse import OptionParser
    usage = """%prog  [path2] [more paths...]"""

    default_repo_url = "http://svn.example.com/MyRepository"
    default_checkout_path = "sparse_trunk"

    parser = OptionParser(usage)
    parser.add_option("-r", "--repo_url", type="str", default=default_repo_url, dest="repo_url", help='Repository URL (default: "%s")' % default_repo_url)
    parser.add_option("-l", "--local_path", type="str", default=default_checkout_path, dest="local_path", help='Local checkout path (default: "%s")' % default_checkout_path)

    default_username = getpass.getuser()
    parser.add_option("-u", "--username", type="str", default=default_username, dest="svn_username", help='SVN login username (default: "%s")' % default_username)
    parser.add_option("-p", "--password", type="str", dest="svn_password", help="SVN login password")

    parser.add_option("-v", "--verbose", action="store_true", default=False, dest="verbose", help="Verbose output")
    (options, args) = parser.parse_args()

    client = getSvnClient(options)
    group_sparse_checkout(
        options,
        client,
        options.repo_url,
        map(os.path.relpath, args),
        options.local_path)

7

或者对 /trunk 进行非递归检出,然后只需手动更新你需要的 3 个目录。


1
如果您已经拥有完整的本地副本,可以使用--set-depth命令来删除不需要的子文件夹。
svn update --set-depth=exclude www

请参见:http://blogs.collab.net/subversion/sparse-directories-now-with-exclusion

set-depth 命令支持多个路径。

更新根本地副本不会更改修改文件夹的深度。

要将文件夹恢复为递归检出状态,您可以再次使用 --set-depth 并带上无限参数。

svn update --set-depth=infinity www

0
我为那些使用TortoiseSvn工具的人添加这个信息:要获得相同的功能,您可以在Checkout函数的Checkout Depth部分中使用“Choose items...”按钮,如下图所示:

Images


-1

有点类似。就像 Bobby 所说的:

svn co file:///.../trunk/foo file:///.../trunk/bar file:///.../trunk/hum

你将获得文件夹,但是从Subversion的角度来看,你将获得单独的文件夹。你必须在每个子文件夹上进行单独的提交和更新。

我认为你不能检出部分树,然后将其作为单个实体处理。


-10

没有特别有用的方式。你可以查看子树(就像Bobby Jack的建议一样),但是这样你就失去了以原子方式更新/提交它们的能力;为了做到这一点,它们需要放置在它们共同的父级下面,而一旦你检出了共同的父级,你将下载该父级下面的所有内容。非递归不是一个好的选择,因为你希望更新和提交是递归的。


17
在现实生活中有很多用例,你只想处理大项目中的一小部分组件,而不想检出整个项目。 - Peter
当然,你可以独立地处理这些子树,但我认为DrPizza在这种情况下指的是非原子提交/更新。在某些情况下,这可能会成为一个问题。 - Andry

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