1# Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights 2# reserved. Use of this source code is governed by a BSD-style license that 3# can be found in the LICENSE file 4 5from __future__ import absolute_import 6from exec_util import exec_cmd 7import os 8import sys 9 10if sys.platform == 'win32': 11 # Force use of the git version bundled with depot_tools. 12 git_exe = 'git.bat' 13else: 14 git_exe = 'git' 15 16 17def is_checkout(path): 18 """ Returns true if the path represents a git checkout. """ 19 return os.path.exists(os.path.join(path, '.git')) 20 21 22def is_ancestor(path='.', commit1='HEAD', commit2='master'): 23 """ Returns whether |commit1| is an ancestor of |commit2|. """ 24 cmd = "%s merge-base --is-ancestor %s %s" % (git_exe, commit1, commit2) 25 result = exec_cmd(cmd, path) 26 return result['ret'] == 0 27 28 29def get_hash(path='.', branch='HEAD'): 30 """ Returns the git hash for the specified branch/tag/hash. """ 31 cmd = "%s rev-parse %s" % (git_exe, branch) 32 result = exec_cmd(cmd, path) 33 if result['out'] != '': 34 return result['out'].strip() 35 return 'Unknown' 36 37 38def get_branch_name(path='.', branch='HEAD'): 39 """ Returns the branch name for the specified branch/tag/hash. """ 40 # Returns the branch name if not in detached HEAD state, else an empty string 41 # or "HEAD". 42 cmd = "%s rev-parse --abbrev-ref %s" % (git_exe, branch) 43 result = exec_cmd(cmd, path) 44 if result['out'] != '': 45 name = result['out'].strip() 46 if len(name) > 0 and name != 'HEAD': 47 return name 48 49 # Returns a value like "(HEAD, origin/3729, 3729)". 50 # Ubuntu 14.04 uses Git version 1.9.1 which does not support %D (which 51 # provides the same output but without the parentheses). 52 cmd = "%s log -n 1 --pretty=%%d %s" % (git_exe, branch) 53 result = exec_cmd(cmd, path) 54 if result['out'] != '': 55 return result['out'].strip()[1:-1].split(', ')[-1] 56 return 'Unknown' 57 58 59def get_url(path='.'): 60 """ Returns the origin url for the specified path. """ 61 cmd = "%s config --get remote.origin.url" % git_exe 62 result = exec_cmd(cmd, path) 63 if result['out'] != '': 64 return result['out'].strip() 65 return 'Unknown' 66 67 68def get_commit_number(path='.', branch='HEAD'): 69 """ Returns the number of commits in the specified branch/tag/hash. """ 70 cmd = "%s rev-list --count %s" % (git_exe, branch) 71 result = exec_cmd(cmd, path) 72 if result['out'] != '': 73 return result['out'].strip() 74 return '0' 75 76 77def get_changed_files(path, hash): 78 """ Retrieves the list of changed files. """ 79 if hash == 'unstaged': 80 cmd = "%s diff --name-only" % git_exe 81 elif hash == 'staged': 82 cmd = "%s diff --name-only --cached" % git_exe 83 else: 84 cmd = "%s diff-tree --no-commit-id --name-only -r %s" % (git_exe, hash) 85 result = exec_cmd(cmd, path) 86 if result['out'] != '': 87 files = result['out'] 88 if sys.platform == 'win32': 89 # Convert to Unix line endings. 90 files = files.replace('\r\n', '\n') 91 return files.strip().split("\n") 92 return [] 93 94 95def get_branch_hashes(path='.', branch='HEAD', ref='origin/master'): 96 """ Returns an ordered list of hashes for commits that have been applied since 97 branching from ref. """ 98 cmd = "%s cherry %s %s" % (git_exe, ref, branch) 99 result = exec_cmd(cmd, path) 100 if result['out'] != '': 101 hashes = result['out'] 102 if sys.platform == 'win32': 103 # Convert to Unix line endings. 104 hashes = hashes.replace('\r\n', '\n') 105 # Remove the "+ " or "- " prefix. 106 return [line[2:] for line in hashes.strip().split('\n')] 107 return [] 108 109 110def write_indented_output(output): 111 """ Apply a fixed amount of intent to lines before printing. """ 112 if output == '': 113 return 114 for line in output.split('\n'): 115 line = line.strip() 116 if len(line) == 0: 117 continue 118 sys.stdout.write('\t%s\n' % line) 119 120 121def git_apply_patch_file(patch_path, patch_dir): 122 """ Apply |patch_path| to files in |patch_dir|. """ 123 patch_name = os.path.basename(patch_path) 124 sys.stdout.write('\nApply %s in %s\n' % (patch_name, patch_dir)) 125 126 if not os.path.isfile(patch_path): 127 sys.stdout.write('... patch file does not exist.\n') 128 return 'fail' 129 130 patch_string = open(patch_path, 'rb').read() 131 if sys.platform == 'win32': 132 # Convert the patch to Unix line endings. This is necessary to avoid 133 # whitespace errors with git apply. 134 patch_string = patch_string.replace(b'\r\n', b'\n') 135 136 # Git apply fails silently if not run relative to a respository root. 137 if not is_checkout(patch_dir): 138 sys.stdout.write('... patch directory is not a repository root.\n') 139 return 'fail' 140 141 config = '-p0 --ignore-whitespace' 142 143 # Output patch contents. 144 cmd = '%s apply %s --numstat' % (git_exe, config) 145 result = exec_cmd(cmd, patch_dir, patch_string) 146 write_indented_output(result['out'].replace('<stdin>', patch_name)) 147 148 # Reverse check to see if the patch has already been applied. 149 cmd = '%s apply %s --reverse --check' % (git_exe, config) 150 result = exec_cmd(cmd, patch_dir, patch_string) 151 if result['err'].find('error:') < 0: 152 sys.stdout.write('... already applied (skipping).\n') 153 return 'skip' 154 155 # Normal check to see if the patch can be applied cleanly. 156 cmd = '%s apply %s --check' % (git_exe, config) 157 result = exec_cmd(cmd, patch_dir, patch_string) 158 if result['err'].find('error:') >= 0: 159 sys.stdout.write('... failed to apply:\n') 160 write_indented_output(result['err'].replace('<stdin>', patch_name)) 161 return 'fail' 162 163 # Apply the patch file. This should always succeed because the previous 164 # command succeeded. 165 cmd = '%s apply %s' % (git_exe, config) 166 result = exec_cmd(cmd, patch_dir, patch_string) 167 if result['err'] == '': 168 sys.stdout.write('... successfully applied.\n') 169 else: 170 sys.stdout.write('... successfully applied (with warnings):\n') 171 write_indented_output(result['err'].replace('<stdin>', patch_name)) 172 return 'apply' 173