关于svn:您可以使用Subversion进行部分结帐吗?

关于svn:您可以使用Subversion进行部分结帐吗?

Can you do a partial checkout with Subversion?

如果我在trunk /下有20个目录,每个目录中都有很多文件,并且只需要其中3个目录,那么是否可以仅在trunk /下有这3个目录来进行Subversion签出?


确实,由于我在这里的帖子的评论,看起来稀疏目录似乎是可行的方法。我相信以下应该这样做:

1
2
3
4
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的答案中所述,您还可以进行非递归检出。这是一种较旧且较不灵活的方法,可以实现类似的效果:

1
2
3
4
svn checkout --non-recursive http://svnserver/trunk/proj
svn update trunk/foo
svn update trunk/bar
svn update trunk/baz

Subversion 1.5引入了稀疏签出,这可能对您有用。从文档中:

... sparse directories (or shallow checkouts) ... allows you to easily check out a working copy—or a portion of a working copy—more shallowly than full recursion, with the freedom to bring in previously ignored files and subdirectories at a later time.


我编写了一个脚本来自动执行复杂的稀疏签出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/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)

或对/ trunk执行非递归检出,然后仅对所需的3个目录进行手动更新。


如果已经具有完整的本地副本,则可以使用--set-depth命令删除不需要的子文件夹。

1
svn update --set-depth=exclude www

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

set-depth命令支持多路径。

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

要将文件夹恢复为可退回签出,可以再次将--set-depth与无穷大参数一起使用。

1
svn update --set-depth=infinity www

有点。正如鲍比所说:

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

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

我不相信您可以签出局部树,然后将局部树作为单个实体使用。


并非以任何特别有用的方式,不是。您可以检出子树(如Bobby Jack的建议),但是您将失去自动更新/提交子树的能力。为此,需要将它们放置在其公共父目录下,并且一旦您签出公共父目录,便会下载该父目录下的所有内容。非递归不是一个好的选择,因为您希望更新和提交是递归的。


推荐阅读