175

If I had 20 directories under trunk/ with lots of files in each and only needed 3 of those directories, would it be possible to do a Subversion checkout with only those 3 directories under trunk?

Juuso Ohtonen
  • 8,826
  • 9
  • 65
  • 98
readonly
  • 343,444
  • 107
  • 203
  • 205
  • See also: http://stackoverflow.com/questions/1667986/how-do-i-only-checkout-pull-down-only-parts-of-an-svn-tree-with-tortoisesvn?lq=1 – Nakilon Jun 04 '13 at 10:47

8 Answers8

293

Indeed, thanks to the comments to my post here, it looks like sparse directories are the way to go. I believe the following should do it:

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

Alternatively, --depth immediates instead of empty checks out files and directories in trunk/proj without their contents. That way you can see which directories exist in the repository.


As mentioned in @zigdon's answer, you can also do a non-recursive checkout. This is an older and less flexible way to achieve a similar effect:

svn checkout --non-recursive http://svnserver/trunk/proj
svn update trunk/foo
svn update trunk/bar
svn update trunk/baz
bahrep
  • 29,961
  • 12
  • 103
  • 150
pkaeding
  • 36,513
  • 30
  • 103
  • 141
  • 4
    If I then issue a svn update on the trunk directory will it pull down all the other folders, or just update the ones that have already been retrieved? – Rob Walker Sep 08 '08 at 23:58
  • @Rob: I don't know about sparse checkouts, but the non-recursive method of above will only checkout those you added via update later on and those that were not present when you did the initial checkout. – Mecki Sep 16 '09 at 20:58
  • 2
    I get `Skipped 'prom/foo'` after `svn update --set-depth infinity proj/foo` :( – sam Oct 15 '13 at 23:16
  • 2
    Oh, you have to update the parent (proj/foo) before you can update deeper (proj/foo/boo). – sam Oct 17 '13 at 18:09
  • 4
    This is a good answer and, really, should be the correctly marked one. Thanks pkaeding! – Jimbo Jan 23 '14 at 13:32
  • 1
    You may need to use an intermediate step with `svn update --set-depth immediates proj` so that it makes proj/foo for updating. – Craig Mar 28 '14 at 03:44
  • FYI Since I had a pom.xml file I wanted in the root folder, I used `svn checkout --depth files http://svnserver/trunk/proj`. I was then able to use `svn update --set-depth infinity proj\foo` etc and got the list and folders fine. – peater Apr 24 '14 at 16:16
  • When I replace checkout depth to `--depth immediate` in your first command following updates does not work for me. It says that yes update has been complete but directories are empty. When depth is set to `--depth empty` everything works. – Kamil Oct 17 '16 at 05:29
  • This solution can solve another problem with an access denied error (for example, in TortoiseSVN) in case of a file/directory move from one subtree to another in the same repo in case where both subtrees has checkouted separately. – Andry Apr 18 '17 at 15:41
  • +1 for providing the summary of commands to accomplish this in addition to the link to the Subversion documentation for details. – Christopher Schultz Aug 31 '17 at 21:52
82

Subversion 1.5 introduces sparse checkouts which may be something you might find useful. From the documentation:

... 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.

bahrep
  • 29,961
  • 12
  • 103
  • 150
Richard Morgan
  • 7,601
  • 9
  • 49
  • 86
8

I wrote a script to automate complex sparse checkouts.

#!/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)
kostmo
  • 6,222
  • 4
  • 40
  • 51
7

Or do a non-recursive checkout of /trunk, then just do a manual update on the 3 directories you need.

zigdon
  • 14,573
  • 6
  • 35
  • 54
1

If you already have the full local copy, you can remove unwanted sub folders by using --set-depth command.

svn update --set-depth=exclude www

See: http://blogs.collab.net/subversion/sparse-directories-now-with-exclusion

The set-depth command support multipile paths.

Updating the root local copy will not change the depth of the modified folder.

To restore the folder to being recusively checkingout, you could use --set-depth again with infinity param.

svn update --set-depth=infinity www
Daniele Santi
  • 771
  • 3
  • 24
  • 31
0

I'm adding this information for those using the TortoiseSvn tool: to obtain the OP same functionality, you can use the Choose items... button in the Checkout Depth section of the Checkout function, as shown in the following screenshot:

Images

Zac
  • 4,510
  • 3
  • 36
  • 44
-1

Sort of. As Bobby says:

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

will get the folders, but you will get separate folders from a subversion perspective. You will have to go separate commits and updates on each subfolder.

I don't believe you can checkout a partial tree and then work with the partial tree as a single entity.

Rob Walker
  • 46,588
  • 15
  • 99
  • 136
-10

Not in any especially useful way, no. You can check out subtrees (as in Bobby Jack's suggestion), but then you lose the ability to update/commit them atomically; to do that, they need to be placed under their common parent, and as soon as you check out the common parent, you'll download everything under that parent. Non-recursive isn't a good option, because you want updates and commits to be recursive.

DrPizza
  • 17,882
  • 7
  • 41
  • 53
  • 17
    -1 for an answer which is simply wrong. There are plenty of use cases in real life where you want to work on only a small subset of the components in a large project, and you don't want to check out the whole project. – Peter Mar 15 '12 at 18:40
  • Of cause you can work with these subtrees independently to each other, but i think DrPizza meant non atomic commits/updates in this case. And it can be a problem in a certain condition. – Andry Apr 18 '17 at 15:29