def cached_file_exists(commit_rev, filename): """A wrapper around git.file_exists but with a cache... ... to avoid repetitive calls to git. PARAMETERS commit_rev: Same as git.file_exists. filename: Same as git.file_exists. """ # Implement the cache as an attribute of this function, # where the key is a tuple (commit_rev, filename), and # the value the result of the query. if 'cache' not in cached_file_exists.__dict__: # First time call, initialize the attribute. cached_file_exists.cache = {} key = (commit_rev, filename) if key not in cached_file_exists.cache: cached_file_exists.cache[key] = file_exists(commit_rev, filename) return cached_file_exists.cache[key]
def style_check_files(filename_list, commit_rev, project_name): """Check a file for style violations if appropriate. Raise InvalidUpdate if one or more style violations are detected. PARAMETERS filename_list: The name of the file to check (an iterable). commit_rev: The associated commit sha1. This piece of information helps us find the correct version of the files to be checked, as well as the .gitattributes files which are used to determine whether pre-commit-checks should be applied or not. project_name: The name of the project (same as the attribute in updates.emails.EmailInfo). """ debug( "style_check_files (commit_rev=%s):\n%s" % (commit_rev, "\n".join([" - `%s'" % fname for fname in filename_list])), level=3, ) config_file = git_config("hooks.style-checker-config-file") # Auxilary list of files we need to fetch from the same reference # for purposes other than checking their contents. aux_files = [] if config_file is not None and config_file not in filename_list: if not file_exists(commit_rev, config_file): info = (STYLE_CHECKER_CONFIG_FILE_MISSING_ERR_MSG % { "config_filename": config_file, "commit_rev": commit_rev }).splitlines() raise InvalidUpdate(*info) aux_files.append(config_file) # Get a copy of all the files and save them in our scratch dir. # In order to allow us to call the style-checker using # the full path (from the project's root directory) of # the files being checked, we re-create the path to those # filenames, and then copy the files at the same path. # # Providing the path as part of the filename argument is useful, # because it allows the messages printed by the style-checker # to be unambiguous in the situation where the same project # has multiple files sharing the same name. More generally, # it can also be useful to quickly locate a file in the project # when trying to make the needed corrections outlined by the # style-checker. for filename in itertools.chain(filename_list, aux_files): path_to_filename = "%s/%s" % (utils.scratch_dir, os.path.dirname(filename)) if not os.path.exists(path_to_filename): os.makedirs(path_to_filename) git.show( "%s:%s" % (commit_rev, filename), _outfile="%s/%s" % (utils.scratch_dir, filename), ) # Call the style-checker. # For testing purposes, provide a back-door allowing the user # to override the style-checking program to be used. That way, # the testsuite has a way to control what the program returns, # and easily test all execution paths without having to maintain # some sources specifically designed to trigger the various # error conditions. style_checker_hook = ThirdPartyHook("hooks.style-checker") if "GIT_HOOKS_STYLE_CHECKER" in os.environ: style_checker_hook.hook_exe = os.environ["GIT_HOOKS_STYLE_CHECKER"] style_checker_hook_args = [] if config_file is not None: style_checker_hook_args.extend(["--config", config_file]) style_checker_hook_args.append(project_name) _, p, out = style_checker_hook.call( hook_input="\n".join(filename_list), hook_args=style_checker_hook_args, cwd=utils.scratch_dir, ) if p.returncode != 0: info = ["pre-commit check failed for commit: %s" % commit_rev ] + out.splitlines() raise InvalidUpdate(*info) # If we reach this point, it means that the style-checker returned # zero (success). Print any output, it might be a non-fatal warning. if out: warn(*out.splitlines())
def __check_gitreview_defaultbranch(self): """If .gitreview exists, validate the defaultbranch value. This is meant to catch the situation where a user creates a new branch for a repository hosted on gerrit. Those repositories typically have a .gitreview file at their root, providing various information, one of them being the default branch name when sending patches for review. When creating a new branch, it is very easy (and frequent) for a user to forget to also update the .gitreview file, causing patch reviews to be sent with the wrong branch, which later then causes the patch to be merged (submitted) on the wrong branch once it is approved. We try to avoid that situation by reading the contents of those files at branch creation time, and reporting an error if it exists and points to a branch name different from ours. Note that we only do that for the traditional git branches. We don't worry about the branches in the gerrit-specific special namespaces, for which a user checking out the branch and sending a review is unlikely. """ GITREVIEW_FILENAME = '.gitreview' DEFAULTBRANCH_CONFIG_NAME = 'gerrit.defaultbranch' # Only perform this check for traditional git branches. # See method description above for the reason why. if not self.ref_name.startswith('refs/heads/'): return if self.search_config_option_list('hooks.no-precommit-check')\ is not None: # The user explicitly disabled the .gitreview check # on this branch. return # If the file doesn't exist for that branch, then there is # no problem. if not file_exists(self.new_rev, GITREVIEW_FILENAME): return # Get the contents of the gitreview file, and then get git # to parse its contents. We process it all into a dictionary # rather than just query the value of the one config we are # looking for, for a couple of reasons: # 1. This allows us to avoid having to git returning with # and error status when the file does not have the config # entry we are looking for (git returns error code 1 # in that case); # 2. If we even want to look at other configurations in # that file, the code is already in place to do so. gitreview_contents = git.show('%s:%s' % (self.new_rev, GITREVIEW_FILENAME)) gitreview_configs = git.config('-z', '-l', '--file', '-', _input=gitreview_contents).split('\x00') config_map = {} for config in gitreview_configs: if not config: # "git config -z" adds a nul character at the end of # its output, which cause gitreview_configs to end with # an empty entry. Just ignore those. continue config_name, config_val = config.split('\n', 1) config_map[config_name] = config_val if DEFAULTBRANCH_CONFIG_NAME in config_map and \ config_map[DEFAULTBRANCH_CONFIG_NAME] != self.short_ref_name: raise InvalidUpdate( "Incorrect gerrit default branch name in file `%s'." % GITREVIEW_FILENAME, "You probably forgot to update your %s file following" % GITREVIEW_FILENAME, "the creation of this branch.", '', "Please create a commit which updates the value", "of %s in the file `%s'" % (DEFAULTBRANCH_CONFIG_NAME, GITREVIEW_FILENAME), "and set it to `%s' (instead of `%s')." % (self.short_ref_name, config_map[DEFAULTBRANCH_CONFIG_NAME]))