| #!/usr/bin/env python |
| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| # Usage: |
| # gclient-new-workdir.py [options] <repository> <new_workdir> |
| # |
| |
| from __future__ import print_function |
| |
| import argparse |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import textwrap |
| |
| import git_common |
| |
| |
| def parse_options(): |
| if sys.platform == 'win32': |
| print('ERROR: This script cannot run on Windows because it uses symlinks.') |
| sys.exit(1) |
| |
| parser = argparse.ArgumentParser(description='''\ |
| Clone an existing gclient directory, taking care of all sub-repositories. |
| Works similarly to 'git new-workdir'.''') |
| parser.add_argument('repository', type=os.path.abspath, |
| help='should contain a .gclient file') |
| parser.add_argument('new_workdir', help='must not exist') |
| parser.add_argument('--reflink', action='store_true', default=None, |
| help='''force to use "cp --reflink" for speed and disk |
| space. need supported FS like btrfs or ZFS.''') |
| parser.add_argument('--no-reflink', action='store_false', dest='reflink', |
| help='''force not to use "cp --reflink" even on supported |
| FS like btrfs or ZFS.''') |
| args = parser.parse_args() |
| |
| if not os.path.exists(args.repository): |
| parser.error('Repository "%s" does not exist.' % args.repository) |
| |
| gclient = os.path.join(args.repository, '.gclient') |
| if not os.path.exists(gclient): |
| parser.error('No .gclient file at "%s".' % gclient) |
| |
| if os.path.exists(args.new_workdir): |
| parser.error('New workdir "%s" already exists.' % args.new_workdir) |
| |
| return args |
| |
| |
| def support_cow(src, dest): |
| # 'cp --reflink' always succeeds when 'src' is a symlink or a directory |
| assert os.path.isfile(src) and not os.path.islink(src) |
| try: |
| subprocess.check_output(['cp', '-a', '--reflink', src, dest], |
| stderr=subprocess.STDOUT) |
| except subprocess.CalledProcessError: |
| return False |
| finally: |
| if os.path.isfile(dest): |
| os.remove(dest) |
| return True |
| |
| |
| def try_vol_snapshot(src, dest): |
| try: |
| subprocess.check_call(['btrfs', 'subvol', 'snapshot', src, dest], |
| stderr=subprocess.STDOUT) |
| except (subprocess.CalledProcessError, OSError): |
| return False |
| return True |
| |
| |
| def main(): |
| args = parse_options() |
| |
| gclient = os.path.join(args.repository, '.gclient') |
| if os.path.islink(gclient): |
| gclient = os.path.realpath(gclient) |
| new_gclient = os.path.join(args.new_workdir, '.gclient') |
| |
| if try_vol_snapshot(args.repository, args.new_workdir): |
| args.reflink = True |
| else: |
| os.makedirs(args.new_workdir) |
| if args.reflink is None: |
| args.reflink = support_cow(gclient, new_gclient) |
| if args.reflink: |
| print('Copy-on-write support is detected.') |
| os.symlink(gclient, new_gclient) |
| |
| for root, dirs, _ in os.walk(args.repository): |
| if '.git' in dirs: |
| workdir = root.replace(args.repository, args.new_workdir, 1) |
| print('Creating: %s' % workdir) |
| |
| if args.reflink: |
| if not os.path.exists(workdir): |
| print('Copying: %s' % workdir) |
| subprocess.check_call(['cp', '-a', '--reflink', root, workdir]) |
| shutil.rmtree(os.path.join(workdir, '.git')) |
| |
| git_common.make_workdir(os.path.join(root, '.git'), |
| os.path.join(workdir, '.git')) |
| if args.reflink: |
| subprocess.check_call(['cp', '-a', '--reflink', |
| os.path.join(root, '.git', 'index'), |
| os.path.join(workdir, '.git', 'index')]) |
| else: |
| subprocess.check_call(['git', 'checkout', '-f'], cwd=workdir) |
| |
| if args.reflink: |
| print(textwrap.dedent('''\ |
| The repo was copied with copy-on-write, and the artifacts were retained. |
| More details on http://crbug.com/721585. |
| |
| Depending on your usage pattern, you might want to do "gn gen" |
| on the output directories. More details: http://crbug.com/723856.''')) |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |