pull_from_git.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #
  2. # Copyright (c) Contributors to the Open 3D Engine Project.
  3. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. import argparse
  9. import sys
  10. import os
  11. import subprocess
  12. import platform
  13. from urllib.parse import urlparse
  14. def SwitchRootDirectory(destinationDir):
  15. """
  16. @returns a tuple previousDir, currentDir if successful
  17. """
  18. currentDir = os.getcwd()
  19. destDir = currentDir
  20. print(f"Current dir is {currentDir}")
  21. if destinationDir is not None:
  22. if not os.path.isabs(destinationDir):
  23. destDir = os.path.join(currentDir, destinationDir)
  24. else:
  25. destDir = destinationDir
  26. if not os.path.isdir(destDir):
  27. raise Exception(f"{destDir} is NOT a directory")
  28. # Change working directory before cloning the project
  29. if currentDir != destDir:
  30. os.chdir(destDir)
  31. return currentDir, destDir
  32. def SubpArgs(args):
  33. """
  34. According to subcommand, when using shell=True, its recommended not to pass in an argument list but the full command line as a single string.
  35. That means in the argument list in the configuration make sure to provide the proper escapements or double-quotes for paths with spaces
  36. :param args: The list of arguments to transform
  37. """
  38. argString = " ".join([arg for arg in args])
  39. print(f"Command: {argString}")
  40. return argString
  41. def GetFilenameFromUrl(gitUrl):
  42. aPath = urlparse(gitUrl)
  43. filenameWithExtension = os.path.basename(aPath.path)
  44. return os.path.splitext(filenameWithExtension)[0]
  45. def DeleteFolder(folder):
  46. """
  47. Use the system's remove folder command instead of os.rmdir
  48. """
  49. if platform.system() == 'Windows':
  50. callResult = subprocess.run(subp_args(['rmdir', '/Q', '/S', str(folder.name)]),
  51. shell=True,
  52. capture_output=True,
  53. cwd=str(folder.parent.absolute()))
  54. else:
  55. callResult = subprocess.run(subp_args(['rm', '-rf', str(folder.name)]),
  56. shell=True,
  57. capture_output=True,
  58. cwd=str(folder.parent.absolute()))
  59. if callResult.returncode != 0:
  60. raise Exception(f"Unable to delete folder {str(folder)}: {str(call_result.stderr)}")
  61. def IsGitProjectCheckedOut(gitUrl, projectName, currentParentDir):
  62. """
  63. @param gitUrl The github url of the project
  64. @param projectName [optional] If defined, this will be the local name of the project root folder
  65. @param currentParentDir The full path of the current working directory which is the parent folder of the git project.
  66. """
  67. if projectName is None:
  68. projectName = GetFilenameFromUrl(gitUrl)
  69. projectPath = os.path.join(currentParentDir, projectName)
  70. if not os.path.exists(projectPath):
  71. return False
  72. # The directory exists. Need to validate it's a valid git project
  73. gitStashCmd = ['git', 'stash']
  74. callResult = subprocess.run(SubpArgs(gitStashCmd),
  75. shell=True,
  76. capture_output=True,
  77. cwd=projectPath)
  78. if callResult.returncode != 0:
  79. # Not a valid git folder, okay to remove and re-clone
  80. DeleteFolder(projectPath)
  81. return False
  82. # Do not re-pull for now.
  83. ## Do a re-pull
  84. #gitPullCmd = ['git', 'pull']
  85. #callResult = subprocess.run(SubpArgs(gitPullCmd),
  86. # shell=True,
  87. # capture_output=True,
  88. # cwd=projectPath)
  89. #if callResult.returncode != 0:
  90. # raise Exception(f"Error pulling source from GitHub: {callResult.stderr.decode('UTF-8', 'ignore')}")
  91. return True
  92. def FetchGitProject(gitUrl, projectName, destinationDir, gitTag, gitCommit):
  93. """
  94. @param gitUrl The github url of the project
  95. @param projectName [optional] If defined, this will be the local name of the project root folder
  96. @param destinationDir [optional] If defined, this is the directory where the project will be cloned into
  97. """
  98. currentDir, destinationDir = SwitchRootDirectory(destinationDir)
  99. # Before cloning see if the repo already exists.
  100. if IsGitProjectCheckedOut(gitUrl, projectName, destinationDir):
  101. print(f"Git project {gitUrl} already cloned under parent directory {destinationDir}")
  102. return
  103. cloneCmd = ['git',
  104. 'clone',
  105. '--single-branch',
  106. '--recursive']
  107. if gitTag is not None:
  108. cloneCmd.extend(['--branch', gitTag])
  109. cloneCmd.append(gitUrl)
  110. if projectName is not None:
  111. cloneCmd.append(projectName)
  112. else:
  113. projectName = GetFilenameFromUrl(gitUrl)
  114. cloneResult = subprocess.run(SubpArgs(cloneCmd),
  115. shell=True,
  116. capture_output=True,
  117. cwd=destinationDir)
  118. if cloneResult.returncode != 0:
  119. raise Exception(f"Error cloning from GitHub: {cloneResult.stderr.decode('UTF-8', 'ignore')}")
  120. if gitCommit is not None:
  121. # Allow the package to specify a specific commit to check out. This is useful for upstream repos that do
  122. # not tag their releases.
  123. # Checking out
  124. checkoutResult = subprocess.run(
  125. SubpArgs(['git', 'checkout', gitCommit]),
  126. shell=True,
  127. capture_output=True,
  128. cwd=projectName)
  129. if checkoutResult.returncode != 0:
  130. raise Exception(f"Error checking out {gitCommit}: {checkoutResult.stderr.decode('UTF-8', 'ignore')}")
  131. print(f"Successfully cloned Git project {gitUrl} under parent directory {destinationDir}")
  132. if __name__ == '__main__':
  133. try:
  134. argParser = argparse.ArgumentParser(
  135. description="Tool to pull a github project if not already pulled",
  136. formatter_class=argparse.RawDescriptionHelpFormatter)
  137. argParser.add_argument('--git-url',
  138. help='The github url of the project',
  139. required=True)
  140. argParser.add_argument('--name',
  141. help='If defined, this will be the local name of the project root folder',
  142. required=False)
  143. argParser.add_argument('--destination-dir',
  144. help='If defined, this is the directory where the project will be cloned into',
  145. required=False)
  146. argParser.add_argument('--git-tag',
  147. help='If defined, the project will be checked out at this tag.',
  148. required=False)
  149. argParser.add_argument('--git-commit',
  150. help='If defined, the project will be checked out at this commit hash.',
  151. required=False)
  152. parsedArgs = argParser.parse_args(sys.argv[1:])
  153. FetchGitProject(
  154. gitUrl = parsedArgs.git_url,
  155. projectName = parsedArgs.name,
  156. destinationDir = parsedArgs.destination_dir,
  157. gitTag = parsedArgs.git_tag,
  158. gitCommit = parsedArgs.git_commit)
  159. except Exception as err:
  160. print(err)
  161. sys.exit(1)