Chromium Code Reviews| Index: tools/git_utils.py |
| diff --git a/tools/git_utils.py b/tools/git_utils.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..910dc47a7086942e996dc1cbeef2999d5c11d9df |
| --- /dev/null |
| +++ b/tools/git_utils.py |
| @@ -0,0 +1,136 @@ |
| +# Copyright 2014 Google Inc. |
| +# |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Module to host the ChangeGitBranch class and test_git_executable function. |
| +""" |
| + |
| +import os |
| +import subprocess |
| +from misc_utils import VerboseSubprocess |
| + |
| + |
| +class ChangeGitBranch(object): |
| + """Class to manage git branches. |
| + |
| + This class allows one to create a new branch in a repository based |
| + off of a given commit, and restore the original tree state. |
| + |
| + Assumes current working directory is a git repository. |
| + |
| + Example: |
| + with ChangeGitBranch('git', 'newBranch'): |
| + edit_files(files) |
| + git_add(files) |
| + git_commit() |
| + git_format_patch('HEAD~') |
| + # At this point, the repository is returned to its original |
| + # state. |
| + |
| + Constructor Args: |
| + git: (string) git executable. |
| + brach_name: (string) if not None, the name of the branch to |
|
borenet
2014/01/30 14:36:35
"branch_name".
Also, please document that this ex
hal.canary
2014/01/30 20:11:02
Done.
|
| + use. If None, then use a temporary branch that will be |
| + deleted. |
| + start_point: (string) if not None, the name of the branch or |
| + commit to branch from. If None, then use origin/master |
|
borenet
2014/01/30 14:36:35
I think upstream_branch or tracking_branch are bet
hal.canary
2014/01/30 20:11:02
Done.
|
| + verbose: (boolean) if true, makes debugging easier. |
| + delete_branch: (boolean) delete the branch afterwards |
| + |
| + Raises: |
| + OSError: failed to execute git. |
| + subprocess.CalledProcessError: git returned unexpected status. |
| + Exception: if the given branch name exists, or if the repository |
| + isn't clean on exit |
| + """ |
| + # pylint: disable=I0011,R0903,R0902 |
| + |
| + def __init__(self, |
| + git, |
| + branch_name, |
| + start_point=None, |
| + verbose=False, |
| + delete_branch=False): |
| + # pylint: disable=I0011,R0913 |
| + self._branch_name = branch_name |
| + if start_point: |
| + self._start_point = start_point |
| + else: |
| + self._start_point = 'origin/master' |
| + self._git = git |
| + self._stash = None |
| + self._original_branch = None |
| + self._vsp = VerboseSubprocess(verbose) |
| + self._delete_branch = delete_branch |
| + |
| + def _has_git_diff(self): |
| + """Return true iff repository has uncommited changes.""" |
| + return bool(self._vsp.call([self._git, 'diff', '--quiet', 'HEAD'])) |
| + |
| + def _branch_exists(self, branch): |
| + """Return true iff branch exists.""" |
| + return 0 == self._vsp.call([self._git, 'show-ref', '--quiet', branch]) |
| + |
| + def __enter__(self): |
| + git, vsp = self._git, self._vsp |
| + |
| + while self._branch_exists(self._branch_name): |
| + self._branch_name += '_' |
| + |
| + self._stash = self._has_git_diff() |
| + if self._stash: |
| + vsp.check_call([git, 'stash', 'save']) |
| + self._original_branch = git_branch_name(git, vsp.verbose) |
| + vsp.check_call( |
| + [git, 'checkout', '-q', '-b', |
| + self._branch_name, self._start_point]) |
| + |
| + def __exit__(self, etype, value, traceback): |
| + # pylint: disable=I0011,R0912 |
| + git, vsp = self._git, self._vsp |
| + |
| + if self._has_git_diff(): |
| + status = vsp.check_output([git, 'status', '-s']) |
| + raise Exception('git checkout not clean:\n%s' % status) |
| + vsp.check_call([git, 'checkout', '-q', self._original_branch]) |
| + if self._stash: |
| + vsp.check_call([git, 'stash', 'pop']) |
| + if self._delete_branch: |
| + assert self._original_branch != self._branch_name |
| + vsp.check_call([git, 'branch', '-D', self._branch_name]) |
| + |
| + |
| +def git_branch_name(git, verbose): |
| + """Return a description of the current branch. |
| + |
| + Args: |
| + git: (string) git executable. |
| + verbose: (boolean) makes debugging easier |
| + |
| + Returns: |
| + A string suitable for passing to `git checkout` later. |
| + """ |
| + vsp = VerboseSubprocess(verbose) |
| + try: |
| + full_branch = vsp.strip_output([git, 'symbolic-ref', 'HEAD']) |
| + return full_branch.split('/')[-1] |
| + except (subprocess.CalledProcessError,): |
| + # "fatal: ref HEAD is not a symbolic ref" |
| + return vsp.strip_output([git, 'rev-parse', 'HEAD']) |
| + |
| + |
| +def test_git_executable(git_executable): |
| + """Test the git executable. |
| + |
| + Args: |
| + git_executable: git executable path. |
| + Returns: |
| + True if test is successful. |
| + """ |
| + with open(os.devnull, 'w') as devnull: |
| + try: |
| + subprocess.call([git_executable, '--version'], stdout=devnull) |
| + except (OSError,): |
| + return False |
| + return True |