gclient-new-workdir.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. #!/usr/bin/env python3
  2. # Copyright 2013 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. #
  6. # Usage:
  7. # gclient-new-workdir.py [options] <repository> <new_workdir>
  8. #
  9. import argparse
  10. import os
  11. import shutil
  12. import subprocess
  13. import sys
  14. import textwrap
  15. import gclient_utils
  16. import git_common
  17. def parse_options():
  18. if sys.platform == 'win32':
  19. print(
  20. 'ERROR: This script cannot run on Windows because it uses symlinks.'
  21. )
  22. sys.exit(1)
  23. if gclient_utils.IsEnvCog():
  24. print('ERROR: This script cannot run in non-git environment.')
  25. sys.exit(1)
  26. parser = argparse.ArgumentParser(description='''\
  27. Clone an existing gclient directory, taking care of all sub-repositories.
  28. Works similarly to 'git new-workdir'.''')
  29. parser.add_argument('repository',
  30. type=os.path.abspath,
  31. help='should contain a .gclient file')
  32. parser.add_argument('new_workdir', help='must not exist')
  33. parser.add_argument('--reflink',
  34. action='store_true',
  35. default=None,
  36. help='''force to use "cp --reflink" for speed and disk
  37. space. need supported FS like btrfs or ZFS.''')
  38. parser.add_argument(
  39. '--no-reflink',
  40. action='store_false',
  41. dest='reflink',
  42. help='''force not to use "cp --reflink" even on supported
  43. FS like btrfs or ZFS.''')
  44. args = parser.parse_args()
  45. if not os.path.exists(args.repository):
  46. parser.error('Repository "%s" does not exist.' % args.repository)
  47. gclient = os.path.join(args.repository, '.gclient')
  48. if not os.path.exists(gclient):
  49. parser.error('No .gclient file at "%s".' % gclient)
  50. if os.path.exists(args.new_workdir):
  51. parser.error('New workdir "%s" already exists.' % args.new_workdir)
  52. return args
  53. def support_cow(src, dest):
  54. # 'cp --reflink' always succeeds when 'src' is a symlink or a directory
  55. assert os.path.isfile(src) and not os.path.islink(src)
  56. try:
  57. subprocess.check_output(['cp', '-a', '--reflink', src, dest],
  58. stderr=subprocess.STDOUT)
  59. except subprocess.CalledProcessError:
  60. return False
  61. finally:
  62. if os.path.isfile(dest):
  63. os.remove(dest)
  64. return True
  65. def try_vol_snapshot(src, dest):
  66. try:
  67. subprocess.check_call(['btrfs', 'subvol', 'snapshot', src, dest],
  68. stderr=subprocess.STDOUT)
  69. except (subprocess.CalledProcessError, OSError):
  70. return False
  71. return True
  72. def main():
  73. args = parse_options()
  74. gclient = os.path.join(args.repository, '.gclient')
  75. if os.path.islink(gclient):
  76. gclient = os.path.realpath(gclient)
  77. new_gclient = os.path.join(args.new_workdir, '.gclient')
  78. if try_vol_snapshot(args.repository, args.new_workdir):
  79. args.reflink = True
  80. else:
  81. os.makedirs(args.new_workdir)
  82. if args.reflink is None:
  83. args.reflink = support_cow(gclient, new_gclient)
  84. if args.reflink:
  85. print('Copy-on-write support is detected.')
  86. os.symlink(gclient, new_gclient)
  87. for root, dirs, _ in os.walk(args.repository):
  88. if '.git' in dirs:
  89. workdir = root.replace(args.repository, args.new_workdir, 1)
  90. print('Creating: %s' % workdir)
  91. if args.reflink:
  92. if not os.path.exists(workdir):
  93. print('Copying: %s' % workdir)
  94. subprocess.check_call(
  95. ['cp', '-a', '--reflink', root, workdir])
  96. shutil.rmtree(os.path.join(workdir, '.git'))
  97. git_common.make_workdir(os.path.join(root, '.git'),
  98. os.path.join(workdir, '.git'))
  99. if args.reflink:
  100. subprocess.check_call([
  101. 'cp', '-a', '--reflink',
  102. os.path.join(root, '.git', 'index'),
  103. os.path.join(workdir, '.git', 'index')
  104. ])
  105. else:
  106. subprocess.check_call(['git', 'checkout', '-f'], cwd=workdir)
  107. if args.reflink:
  108. print(
  109. textwrap.dedent('''\
  110. The repo was copied with copy-on-write, and the artifacts were retained.
  111. More details on http://crbug.com/721585.
  112. Depending on your usage pattern, you might want to do "gn gen"
  113. on the output directories. More details: http://crbug.com/723856.'''))
  114. if __name__ == '__main__':
  115. sys.exit(main())