Add a git-drover.
This uses the same trick as git-new-workdir to reuse an existing git
checkout without interfering with it. However, this makes it only usable
on platforms where os.symlink exists.
BUG=404755
Review URL: https://codereview.chromium.org/1342383002
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@296920 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/gclient-new-workdir.py b/gclient-new-workdir.py
index 6c241c5..c469824 100755
--- a/gclient-new-workdir.py
+++ b/gclient-new-workdir.py
@@ -13,6 +13,8 @@
import sys
import textwrap
+import git_common
+
def print_err(msg):
print >> sys.stderr, msg
@@ -32,7 +34,7 @@
<repository> should contain a .gclient file
<new_workdir> must not exist
- ''' % os.path.basename(sys.argv[0])
+ '''% os.path.basename(sys.argv[0])
print_err(textwrap.dedent(usage_msg))
sys.exit(1)
@@ -70,43 +72,11 @@
for root, dirs, _ in os.walk(repository):
if '.git' in dirs:
workdir = root.replace(repository, new_workdir, 1)
- make_workdir(os.path.join(root, '.git'),
- os.path.join(workdir, '.git'))
-
-
-def make_workdir(repository, new_workdir):
- print('Creating: ' + new_workdir)
- os.makedirs(new_workdir)
-
- GIT_DIRECTORY_WHITELIST = [
- 'config',
- 'info',
- 'hooks',
- 'logs/refs',
- 'objects',
- 'packed-refs',
- 'refs',
- 'remotes',
- 'rr-cache',
- 'svn'
- ]
-
- for entry in GIT_DIRECTORY_WHITELIST:
- make_symlink(repository, new_workdir, entry)
-
- shutil.copy2(os.path.join(repository, 'HEAD'),
- os.path.join(new_workdir, 'HEAD'))
- subprocess.check_call(['git', 'checkout', '-f'],
- cwd=new_workdir.rstrip('.git'))
-
-
-def make_symlink(repository, new_workdir, link):
- if not os.path.exists(os.path.join(repository, link)):
- return
- link_dir = os.path.dirname(os.path.join(new_workdir, link))
- if not os.path.exists(link_dir):
- os.makedirs(link_dir)
- os.symlink(os.path.join(repository, link), os.path.join(new_workdir, link))
+ print('Creating: %s' % workdir)
+ git_common.make_workdir(os.path.join(root, '.git'),
+ os.path.join(workdir, '.git'))
+ subprocess.check_call(['git', 'checkout', '-f'],
+ cwd=new_workdir.rstrip('.git'))
if __name__ == '__main__':
diff --git a/git-drover b/git-drover
new file mode 100755
index 0000000..ff4eba7
--- /dev/null
+++ b/git-drover
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+. $(type -P python_git_runner.sh)
diff --git a/git_common.py b/git_common.py
index 9e0adf2..5a01c83 100644
--- a/git_common.py
+++ b/git_common.py
@@ -22,6 +22,7 @@
import logging
import os
import re
+import shutil
import signal
import sys
import tempfile
@@ -817,3 +818,38 @@
missing_upstreams[info.upstream] = None
return dict(info_map.items() + missing_upstreams.items())
+
+
+def make_workdir_common(repository, new_workdir, files_to_symlink,
+ files_to_copy):
+ os.makedirs(new_workdir)
+ for entry in files_to_symlink:
+ clone_file(repository, new_workdir, entry, os.symlink)
+ for entry in files_to_copy:
+ clone_file(repository, new_workdir, entry, shutil.copy)
+
+
+def make_workdir(repository, new_workdir):
+ GIT_DIRECTORY_WHITELIST = [
+ 'config',
+ 'info',
+ 'hooks',
+ 'logs/refs',
+ 'objects',
+ 'packed-refs',
+ 'refs',
+ 'remotes',
+ 'rr-cache',
+ 'svn'
+ ]
+ make_workdir_common(repository, new_workdir, GIT_DIRECTORY_WHITELIST,
+ ['HEAD'])
+
+
+def clone_file(repository, new_workdir, link, operation):
+ if not os.path.exists(os.path.join(repository, link)):
+ return
+ link_dir = os.path.dirname(os.path.join(new_workdir, link))
+ if not os.path.exists(link_dir):
+ os.makedirs(link_dir)
+ operation(os.path.join(repository, link), os.path.join(new_workdir, link))
diff --git a/git_drover.py b/git_drover.py
new file mode 100755
index 0000000..18756d8
--- /dev/null
+++ b/git_drover.py
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""git drover: A tool for merging changes to release branches."""
+
+import argparse
+import functools
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import git_common
+
+
+class Error(Exception):
+ pass
+
+
+class _Drover(object):
+
+ def __init__(self, branch, revision, parent_repo, dry_run):
+ self._branch = branch
+ self._branch_ref = 'refs/remotes/branch-heads/%s' % branch
+ self._revision = revision
+ self._parent_repo = os.path.abspath(parent_repo)
+ self._dry_run = dry_run
+ self._workdir = None
+ self._branch_name = None
+ self._dev_null_file = open(os.devnull, 'w')
+
+ def run(self):
+ """Runs this Drover instance.
+
+ Raises:
+ Error: An error occurred while attempting to cherry-pick this change.
+ """
+ try:
+ self._run_internal()
+ finally:
+ self._cleanup()
+
+ def _run_internal(self):
+ self._check_inputs()
+ if not self._confirm('Going to cherry-pick\n"""\n%s"""\nto %s.' % (
+ self._run_git_command(['show', '-s', self._revision]), self._branch)):
+ return
+ self._create_checkout()
+ self._prepare_cherry_pick()
+ if self._dry_run:
+ logging.info('--dry_run enabled; not landing.')
+ return
+
+ self._run_git_command(['cl', 'upload'],
+ error_message='Upload failed',
+ interactive=True)
+
+ if not self._confirm('About to land on %s.' % self._branch):
+ return
+ self._run_git_command(['cl', 'land', '--bypass-hooks'], interactive=True)
+
+ def _cleanup(self):
+ if self._branch_name:
+ try:
+ self._run_git_command(['cherry-pick', '--abort'])
+ except Error:
+ pass
+ self._run_git_command(['checkout', '--detach'])
+ self._run_git_command(['branch', '-D', self._branch_name])
+ if self._workdir:
+ logging.debug('Deleting %s', self._workdir)
+ shutil.rmtree(self._workdir)
+ self._dev_null_file.close()
+
+ @staticmethod
+ def _confirm(message):
+ """Show a confirmation prompt with the given message.
+
+ Returns:
+ A bool representing whether the user wishes to continue.
+ """
+ result = ''
+ while result not in ('y', 'n'):
+ try:
+ result = raw_input('%s Continue (y/n)? ' % message)
+ except EOFError:
+ result = 'n'
+ return result == 'y'
+
+ def _check_inputs(self):
+ """Check the input arguments and ensure the parent repo is up to date."""
+
+ if not os.path.isdir(self._parent_repo):
+ raise Error('Invalid parent repo path %r' % self._parent_repo)
+ if not hasattr(os, 'symlink'):
+ raise Error('Symlink support is required')
+
+ self._run_git_command(['--help'], error_message='Unable to run git')
+ self._run_git_command(['status'],
+ error_message='%r is not a valid git repo' %
+ os.path.abspath(self._parent_repo))
+ self._run_git_command(['fetch', 'origin'],
+ error_message='Failed to fetch origin')
+ self._run_git_command(
+ ['rev-parse', '%s^{commit}' % self._branch_ref],
+ error_message='Branch %s not found' % self._branch_ref)
+ self._run_git_command(
+ ['rev-parse', '%s^{commit}' % self._revision],
+ error_message='Revision "%s" not found' % self._revision)
+
+ FILES_TO_LINK = [
+ 'refs',
+ 'logs/refs',
+ 'info/refs',
+ 'info/exclude',
+ 'objects',
+ 'hooks',
+ 'packed-refs',
+ 'remotes',
+ 'rr-cache',
+ 'svn',
+ ]
+ FILES_TO_COPY = ['config', 'HEAD']
+
+ def _create_checkout(self):
+ """Creates a checkout to use for cherry-picking.
+
+ This creates a checkout similarly to git-new-workdir. Most of the .git
+ directory is shared with the |self._parent_repo| using symlinks. This
+ differs from git-new-workdir in that the config is forked instead of shared.
+ This is so the new workdir can be a sparse checkout without affecting
+ |self._parent_repo|.
+ """
+ parent_git_dir = os.path.abspath(self._run_git_command(
+ ['rev-parse', '--git-dir']).strip())
+ self._workdir = tempfile.mkdtemp(prefix='drover_%s_' % self._branch)
+ logging.debug('Creating checkout in %s', self._workdir)
+ git_dir = os.path.join(self._workdir, '.git')
+ git_common.make_workdir_common(parent_git_dir, git_dir, self.FILES_TO_LINK,
+ self.FILES_TO_COPY)
+ self._run_git_command(['config', 'core.sparsecheckout', 'true'])
+ with open(os.path.join(git_dir, 'info', 'sparse-checkout'), 'w') as f:
+ f.write('codereview.settings')
+
+ branch_name = os.path.split(self._workdir)[-1]
+ self._run_git_command(['checkout', '-b', branch_name, self._branch_ref])
+ self._branch_name = branch_name
+
+ def _prepare_cherry_pick(self):
+ self._run_git_command(['cherry-pick', '-x', self._revision],
+ error_message='Patch failed to apply')
+ self._run_git_command(['reset', '--hard'])
+
+ def _run_git_command(self, args, error_message=None, interactive=False):
+ """Runs a git command.
+
+ Args:
+ args: A list of strings containing the args to pass to git.
+ interactive:
+ error_message: A string containing the error message to report if the
+ command fails.
+
+ Raises:
+ Error: The command failed to complete successfully.
+ """
+ cwd = self._workdir if self._workdir else self._parent_repo
+ logging.debug('Running git %s (cwd %r)', ' '.join('%s' % arg
+ for arg in args), cwd)
+
+ run = subprocess.check_call if interactive else subprocess.check_output
+
+ try:
+ return run(['git'] + args,
+ shell=False,
+ cwd=cwd,
+ stderr=self._dev_null_file)
+ except (OSError, subprocess.CalledProcessError) as e:
+ if error_message:
+ raise Error(error_message)
+ else:
+ raise Error('Command %r failed: %s' % (' '.join(args), e))
+
+
+def cherry_pick_change(branch, revision, parent_repo, dry_run):
+ """Cherry-picks a change into a branch.
+
+ Args:
+ branch: A string containing the release branch number to which to
+ cherry-pick.
+ revision: A string containing the revision to cherry-pick. It can be any
+ string that git-rev-parse can identify as referring to a single
+ revision.
+ parent_repo: A string containing the path to the parent repo to use for this
+ cherry-pick.
+ dry_run: A boolean containing whether to stop before uploading the
+ cherry-pick cl.
+
+ Raises:
+ Error: An error occurred while attempting to cherry-pick |cl| to |branch|.
+ """
+ drover = _Drover(branch, revision, parent_repo, dry_run)
+ drover.run()
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Cherry-pick a change into a release branch.')
+ parser.add_argument(
+ '--branch',
+ type=str,
+ required=True,
+ metavar='<branch>',
+ help='the name of the branch to which to cherry-pick; e.g. 1234')
+ parser.add_argument('--cherry-pick',
+ type=str,
+ required=True,
+ metavar='<change>',
+ help=('the change to cherry-pick; this can be any string '
+ 'that unambiguously refers to a revision'))
+ parser.add_argument(
+ '--parent_checkout',
+ type=str,
+ default=os.path.abspath('.'),
+ metavar='<path_to_parent_checkout>',
+ help=('the path to the chromium checkout to use as the source for a '
+ 'creating git-new-workdir workdir to use for cherry-picking; '
+ 'if unspecified, the current directory is used'))
+ parser.add_argument(
+ '--dry-run',
+ action='store_true',
+ default=False,
+ help=("don't actually upload and land; "
+ "just check that cherry-picking would succeed"))
+ parser.add_argument('-v',
+ '--verbose',
+ action='store_true',
+ default=False,
+ help='show verbose logging')
+ options = parser.parse_args()
+ if options.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+ try:
+ cherry_pick_change(options.branch, options.cherry_pick,
+ options.parent_checkout, options.dry_run)
+ except Error as e:
+ logging.error(e.message)
+ sys.exit(128)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/man/html/git-drover.html b/man/html/git-drover.html
index fc5366d..3171d2a 100644
--- a/man/html/git-drover.html
+++ b/man/html/git-drover.html
@@ -755,7 +755,9 @@
<h2 id="_synopsis">SYNOPSIS</h2>
<div class="sectionbody">
<div class="verseblock">
-<pre class="content"><em>git drover</em></pre>
+<pre class="content"><em>git drover</em> --branch <branch> --cherry-pick <commit>
+ [--parent_checkout <path-to-existing-checkout>]
+ [--verbose] [--dry-run]</pre>
<div class="attribution">
</div></div>
</div>
@@ -763,8 +765,65 @@
<div class="sect1">
<h2 id="_description">DESCRIPTION</h2>
<div class="sectionbody">
-<div class="paragraph"><p><code>git drover</code> is NOT IMPLEMENTED yet. See the EXAMPLE section for the equivalent
-sequence of commands to run.</p></div>
+<div class="paragraph"><p><code>git drover</code> applies a commit to a release branch. It creates a new workdir from
+an existing checkout to avoid downloading a new checkout without affecting the
+existing checkout. Creating a workdir requires symlinks so this does not work on
+Windows. See the EXAMPLE section for the equivalent sequence of commands to run.</p></div>
+<div class="paragraph"><p><code>git drover</code> does not support reverts. See the EXAMPLE section for the
+equivalent sequence of commands to run.</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_options">OPTIONS</h2>
+<div class="sectionbody">
+<div class="dlist"><dl>
+<dt class="hdlist1">
+--branch <branch>
+</dt>
+<dd>
+<p>
+ The branch to cherry-pick the commit to.
+</p>
+</dd>
+<dt class="hdlist1">
+--cherry-pick <commit>
+</dt>
+<dd>
+<p>
+ The commit to cherry-pick.
+</p>
+</dd>
+<dt class="hdlist1">
+--parent_checkout
+</dt>
+<dd>
+<p>
+ The path to the chromium checkout to use as the source for a creating
+ git-new-workdir workdir to use for cherry-picking. If unspecified, the current
+ directory is used.
+</p>
+</dd>
+<dt class="hdlist1">
+-v
+</dt>
+<dt class="hdlist1">
+--verbose
+</dt>
+<dd>
+<p>
+ Enable verbose logging.
+</p>
+</dd>
+<dt class="hdlist1">
+--dry-run
+</dt>
+<dd>
+<p>
+ Skip landing the cherry-pick. Just ensure that the commit can be cherry-picked
+ into the branch.
+</p>
+</dd>
+</dl></div>
</div>
</div>
<div class="sect1">
@@ -777,12 +836,9 @@
at least once to fetch the branches.</p></div>
<div class="sect3">
<h4 id="_merge_example">Merge Example</h4>
-<div class="paragraph"><p></p></div><div class="listingblock"><div class="content"><pre><code># Make sure we have the most up-to-date branch sources.
-<span style="font-weight: bold; color: #ffffff">$ git fetch</span>
-
-# Here's a commit (from some.committer) that we want to 'drover'.
+<div class="paragraph"><p></p></div><div class="listingblock"><div class="content"><pre><code># Here's a commit (from some.committer) that we want to 'drover'.
<span style="font-weight: bold; color: #ffffff">$ git log -n 1 --pretty=fuller</span>
-commit 4a00a0c3c1bb01f11b42cb70f3ad587026cec02b
+<span style="color: #e7e71c">commit 8b79b7b2f7e6e728f9a3c7b385c72efc7c47244a</span>
Author: some.committer <[email protected]>
AuthorDate: Thu Apr 10 08:54:46 2014 +0000
Commit: some.committer <[email protected]>
@@ -790,37 +846,22 @@
This change needs to go to branch 9999
-# Checkout the branch we want to 'drover' to.
-<span style="font-weight: bold; color: #ffffff">$ git checkout -b drover_9999 branch-heads/9999</span>
-Branch drover_9999 set up to track remote ref refs/branch-heads/9999.
-
# Now do the 'drover'.
-# IMPORTANT!!! Do Not leave off the '-x' flag
-<span style="font-weight: bold; color: #ffffff">$ git cherry-pick -x 4a00a0c3c1bb01f11b42cb70f3ad587026cec02b</span>
-[drover_9999 19d3d0b] This change needs to go to branch 9999
- Author: some.committer <[email protected]>
- Date: Thu Apr 10 08:54:46 2014 +0000
- 1 file changed, 1 insertion(+)
- create mode 100644 modified_file
-
-# That took the code authored by some.committer and committed it to
-# the branch by the person who drovered it (i.e. you).
-<span style="font-weight: bold; color: #ffffff">$ git log -n 1 --pretty=fuller</span>
-commit 19d3d0b9d8f802df8e2fd563cbc919679d310ecd
-Author: some.committer <[email protected]>
-AuthorDate: Thu Apr 10 08:54:46 2014 +0000
-Commit: you <[email protected]>
-CommitDate: Thu Apr 10 09:11:36 2014 +0000
+<span style="font-weight: bold; color: #ffffff">$ git drover --branch 9999 --cherry-pick 8b79b7b2f7e6e728f9a3c7b385c72efc7c47244a</span>
+Going to cherry-pick
+"""
+<span style="color: #e7e71c">commit 8b79b7b2f7e6e728f9a3c7b385c72efc7c47244a</span>
+Author: some.committer <[email protected]>
+Date: Thu Apr 10 08:54:46 2014 +0000
This change needs to go to branch 9999
+"""
+to 9999. Continue (y/n)? y
- (cherry picked from commit 4a00a0c3c1bb01f11b42cb70f3ad587026cec02b)
+# A cl is uploaded to rietveld, where it can be reviewed before landing.
-# Looks good. Ship it!
-<span style="font-weight: bold; color: #ffffff">$ git cl upload</span>
-# Wait for LGTM or TBR it.
-<span style="font-weight: bold; color: #ffffff">$ git cl land</span>
-# Or skip the LGTM/TBR and just 'git cl land --bypass-hooks'
+About to land on 9999. Continue (y/n)? y
+# The cherry-pick cl is landed on the branch 9999.
</code></pre></div></div><p><div class="paragraph"></p></div>
</div>
<div class="sect3">
@@ -834,30 +875,78 @@
# Here's the commit we want to revert.
<span style="font-weight: bold; color: #ffffff">$ git log -n 1</span>
-commit 590b333cbc04d13da67b2a1c5282835d4f27e398
+<span style="color: #e7e71c">commit 33b0e9164d4564eb8a4b4e5b951bba6edeeecacb</span>
Author: some.committer <[email protected]>
Date: Thu Apr 10 08:54:46 2014 +0000
This change is horribly broken.
# Now do the revert.
-<span style="font-weight: bold; color: #ffffff">$ git revert 590b333cbc04d13da67b2a1c5282835d4f27e398</span>
+<span style="font-weight: bold; color: #ffffff">$ git revert 33b0e9164d4564eb8a4b4e5b951bba6edeeecacb</span>
# That reverted the change and committed the revert.
<span style="font-weight: bold; color: #ffffff">$ git log -n 1</span>
-commit 6f541155a9adf98f4e7f94dd561d022fb022d43f
+<span style="color: #e7e71c">commit 8a2d2bb98b9cfc9260a9bc86da1eec2a43f43f8b</span>
Author: you <[email protected]>
Date: Thu Apr 10 09:11:36 2014 +0000
Revert "This change is horribly broken."
- This reverts commit 590b333cbc04d13da67b2a1c5282835d4f27e398.
+ This reverts commit 33b0e9164d4564eb8a4b4e5b951bba6edeeecacb.
# As with old drover, reverts are generally OK to commit without LGTM.
<span style="font-weight: bold; color: #ffffff">$ git cl upload -r [email protected] --send-mail</span>
<span style="font-weight: bold; color: #ffffff">$ git cl land --bypass-hooks</span>
</code></pre></div></div><p><div class="paragraph"></p></div>
</div>
+<div class="sect3">
+<h4 id="_manual_merge_example">Manual Merge Example</h4>
+<div class="paragraph"><p></p></div><div class="listingblock"><div class="content"><pre><code># Make sure we have the most up-to-date branch sources.
+<span style="font-weight: bold; color: #ffffff">$ git fetch</span>
+
+# Here's a commit (from some.committer) that we want to 'drover'.
+<span style="font-weight: bold; color: #ffffff">$ git log -n 1 --pretty=fuller</span>
+<span style="color: #e7e71c">commit 537f446fa3d5e41acab017bb0b082fbd0c9eb043</span>
+Author: some.committer <[email protected]>
+AuthorDate: Thu Apr 10 08:54:46 2014 +0000
+Commit: some.committer <[email protected]>
+CommitDate: Thu Apr 10 08:54:46 2014 +0000
+
+ This change needs to go to branch 9999
+
+# Checkout the branch we want to 'drover' to.
+<span style="font-weight: bold; color: #ffffff">$ git checkout -b drover_9999 branch-heads/9999</span>
+Branch drover_9999 set up to track remote ref refs/branch-heads/9999.
+
+# Now do the 'drover'.
+# IMPORTANT!!! Do Not leave off the '-x' flag
+<span style="font-weight: bold; color: #ffffff">$ git cherry-pick -x 537f446fa3d5e41acab017bb0b082fbd0c9eb043</span>
+[drover_9999 b468abc] This change needs to go to branch 9999
+ Author: some.committer <[email protected]>
+ Date: Thu Apr 10 08:54:46 2014 +0000
+ 1 file changed, 1 insertion(+)
+ create mode 100644 modified_file
+
+# That took the code authored by some.committer and committed it to
+# the branch by the person who drovered it (i.e. you).
+<span style="font-weight: bold; color: #ffffff">$ git log -n 1 --pretty=fuller</span>
+<span style="color: #e7e71c">commit b468abc42ddd4fd9aecc48c3eda172265306d2b4</span>
+Author: some.committer <[email protected]>
+AuthorDate: Thu Apr 10 08:54:46 2014 +0000
+Commit: you <[email protected]>
+CommitDate: Thu Apr 10 09:11:36 2014 +0000
+
+ This change needs to go to branch 9999
+
+ (cherry picked from commit 537f446fa3d5e41acab017bb0b082fbd0c9eb043)
+
+# Looks good. Ship it!
+<span style="font-weight: bold; color: #ffffff">$ git cl upload</span>
+# Wait for LGTM or TBR it.
+<span style="font-weight: bold; color: #ffffff">$ git cl land</span>
+# Or skip the LGTM/TBR and just 'git cl land --bypass-hooks'
+</code></pre></div></div><p><div class="paragraph"></p></div>
+</div>
</div>
</div>
</div>
@@ -879,7 +968,7 @@
<div id="footnotes"><hr /></div>
<div id="footer">
<div id="footer-text">
-Last updated 2014-09-09 13:42:13 PDT
+Last updated 2015-09-23 11:11:58 AEST
</div>
</div>
</body>
diff --git a/man/man1/git-drover.1 b/man/man1/git-drover.1
index 17779cc..2041fa1 100644
--- a/man/man1/git-drover.1
+++ b/man/man1/git-drover.1
@@ -1,13 +1,13 @@
'\" t
.\" Title: git-drover
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
-.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
-.\" Date: 09/09/2014
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 09/23/2015
.\" Manual: Chromium depot_tools Manual
-.\" Source: depot_tools 6e7202b
+.\" Source: depot_tools 4549a59
.\" Language: English
.\"
-.TH "GIT\-DROVER" "1" "09/09/2014" "depot_tools 6e7202b" "Chromium depot_tools Manual"
+.TH "GIT\-DROVER" "1" "09/23/2015" "depot_tools 4549a59" "Chromium depot_tools Manual"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
@@ -32,12 +32,42 @@
.SH "SYNOPSIS"
.sp
.nf
-\fIgit drover\fR
+\fIgit drover\fR \-\-branch <branch> \-\-cherry\-pick <commit>
+ [\-\-parent_checkout <path\-to\-existing\-checkout>]
+ [\-\-verbose] [\-\-dry\-run]
.fi
.sp
.SH "DESCRIPTION"
.sp
-git drover is NOT IMPLEMENTED yet\&. See the EXAMPLE section for the equivalent sequence of commands to run\&.
+git drover applies a commit to a release branch\&. It creates a new workdir from an existing checkout to avoid downloading a new checkout without affecting the existing checkout\&. Creating a workdir requires symlinks so this does not work on Windows\&. See the EXAMPLE section for the equivalent sequence of commands to run\&.
+.sp
+git drover does not support reverts\&. See the EXAMPLE section for the equivalent sequence of commands to run\&.
+.SH "OPTIONS"
+.PP
+\-\-branch <branch>
+.RS 4
+The branch to cherry\-pick the commit to\&.
+.RE
+.PP
+\-\-cherry\-pick <commit>
+.RS 4
+The commit to cherry\-pick\&.
+.RE
+.PP
+\-\-parent_checkout
+.RS 4
+The path to the chromium checkout to use as the source for a creating git\-new\-workdir workdir to use for cherry\-picking\&. If unspecified, the current directory is used\&.
+.RE
+.PP
+\-v, \-\-verbose
+.RS 4
+Enable verbose logging\&.
+.RE
+.PP
+\-\-dry\-run
+.RS 4
+Skip landing the cherry\-pick\&. Just ensure that the commit can be cherry\-picked into the branch\&.
+.RE
.SH "EXAMPLE"
.SS "PREREQUISITES"
.sp
@@ -57,12 +87,9 @@
.RS 4
.\}
.nf
-# Make sure we have the most up\-to\-date branch sources\&.
-\fB$ git fetch\fR
-
# Here\*(Aqs a commit (from some\&.committer) that we want to \*(Aqdrover\*(Aq\&.
\fB$ git log \-n 1 \-\-pretty=fuller\fR
-commit 0421d3583f73220c8f88b1a96898fcd81222fe73
+commit b5a049e34297f22a4ea63567b32e3290bb3f244c
Author: some\&.committer <some\&.committer@chromium\&.org>
AuthorDate: Thu Apr 10 08:54:46 2014 +0000
Commit: some\&.committer <some\&.committer@chromium\&.org>
@@ -70,37 +97,22 @@
This change needs to go to branch 9999
-# Checkout the branch we want to \*(Aqdrover\*(Aq to\&.
-\fB$ git checkout \-b drover_9999 branch\-heads/9999\fR
-Branch drover_9999 set up to track remote ref refs/branch\-heads/9999\&.
-
# Now do the \*(Aqdrover\*(Aq\&.
-# IMPORTANT!!! Do Not leave off the \*(Aq\-x\*(Aq flag
-\fB$ git cherry\-pick \-x 0421d3583f73220c8f88b1a96898fcd81222fe73\fR
-[drover_9999 5c0a17d] This change needs to go to branch 9999
- Author: some\&.committer <some\&.committer@chromium\&.org>
- Date: Thu Apr 10 08:54:46 2014 +0000
- 1 file changed, 1 insertion(+)
- create mode 100644 modified_file
-
-# That took the code authored by some\&.committer and committed it to
-# the branch by the person who drovered it (i\&.e\&. you)\&.
-\fB$ git log \-n 1 \-\-pretty=fuller\fR
-commit 5c0a17dd382cd098182ac9f486ccd6b86c28d96e
-Author: some\&.committer <some\&.committer@chromium\&.org>
-AuthorDate: Thu Apr 10 08:54:46 2014 +0000
-Commit: you <you@chromium\&.org>
-CommitDate: Thu Apr 10 09:11:36 2014 +0000
+\fB$ git drover \-\-branch 9999 \-\-cherry\-pick b5a049e34297f22a4ea63567b32e3290bb3f244c\fR
+Going to cherry\-pick
+"""
+commit b5a049e34297f22a4ea63567b32e3290bb3f244c
+Author: some\&.committer <some\&.committer@chromium\&.org>
+Date: Thu Apr 10 08:54:46 2014 +0000
This change needs to go to branch 9999
+"""
+to 9999\&. Continue (y/n)? y
- (cherry picked from commit 0421d3583f73220c8f88b1a96898fcd81222fe73)
+# A cl is uploaded to rietveld, where it can be reviewed before landing\&.
-# Looks good\&. Ship it!
-\fB$ git cl upload\fR
-# Wait for LGTM or TBR it\&.
-\fB$ git cl land\fR
-# Or skip the LGTM/TBR and just \*(Aqgit cl land \-\-bypass\-hooks\*(Aq
+About to land on 9999\&. Continue (y/n)? y
+# The cherry\-pick cl is landed on the branch 9999\&.
.fi
.if n \{\
.RE
@@ -131,24 +143,24 @@
# Here\*(Aqs the commit we want to revert\&.
\fB$ git log \-n 1\fR
-commit 28bb44fa7f9d5e19b73a670ae923d3a96dec250a
+commit 215689406a8ca5813412becc6258509be903db59
Author: some\&.committer <some\&.committer@chromium\&.org>
Date: Thu Apr 10 08:54:46 2014 +0000
This change is horribly broken\&.
# Now do the revert\&.
-\fB$ git revert 28bb44fa7f9d5e19b73a670ae923d3a96dec250a\fR
+\fB$ git revert 215689406a8ca5813412becc6258509be903db59\fR
# That reverted the change and committed the revert\&.
\fB$ git log \-n 1\fR
-commit 4618467de1407aa159624015c8c8461ec35fbaf1
+commit 1efaf0e8b1c6c6afadfb37e15023b52b960ac2fd
Author: you <you@chromium\&.org>
Date: Thu Apr 10 09:11:36 2014 +0000
Revert "This change is horribly broken\&."
- This reverts commit 28bb44fa7f9d5e19b73a670ae923d3a96dec250a\&.
+ This reverts commit 215689406a8ca5813412becc6258509be903db59\&.
# As with old drover, reverts are generally OK to commit without LGTM\&.
\fB$ git cl upload \-r some\&.committer@chromium\&.org \-\-send\-mail\fR
@@ -159,6 +171,71 @@
.\}
.sp
.RE
+.sp
+.it 1 an-trap
+.nr an-no-space-flag 1
+.nr an-break-flag 1
+.br
+.ps +1
+\fBManual Merge Example\fR
+.RS 4
+.sp
+
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+# Make sure we have the most up\-to\-date branch sources\&.
+\fB$ git fetch\fR
+
+# Here\*(Aqs a commit (from some\&.committer) that we want to \*(Aqdrover\*(Aq\&.
+\fB$ git log \-n 1 \-\-pretty=fuller\fR
+commit 640f962733bfd2b9c44539a0d65952643750957e
+Author: some\&.committer <some\&.committer@chromium\&.org>
+AuthorDate: Thu Apr 10 08:54:46 2014 +0000
+Commit: some\&.committer <some\&.committer@chromium\&.org>
+CommitDate: Thu Apr 10 08:54:46 2014 +0000
+
+ This change needs to go to branch 9999
+
+# Checkout the branch we want to \*(Aqdrover\*(Aq to\&.
+\fB$ git checkout \-b drover_9999 branch\-heads/9999\fR
+Branch drover_9999 set up to track remote ref refs/branch\-heads/9999\&.
+
+# Now do the \*(Aqdrover\*(Aq\&.
+# IMPORTANT!!! Do Not leave off the \*(Aq\-x\*(Aq flag
+\fB$ git cherry\-pick \-x 640f962733bfd2b9c44539a0d65952643750957e\fR
+[drover_9999 5f1ae97] This change needs to go to branch 9999
+ Author: some\&.committer <some\&.committer@chromium\&.org>
+ Date: Thu Apr 10 08:54:46 2014 +0000
+ 1 file changed, 1 insertion(+)
+ create mode 100644 modified_file
+
+# That took the code authored by some\&.committer and committed it to
+# the branch by the person who drovered it (i\&.e\&. you)\&.
+\fB$ git log \-n 1 \-\-pretty=fuller\fR
+commit 5f1ae978a8d05c16d8ed812163b7aa927f028bf9
+Author: some\&.committer <some\&.committer@chromium\&.org>
+AuthorDate: Thu Apr 10 08:54:46 2014 +0000
+Commit: you <you@chromium\&.org>
+CommitDate: Thu Apr 10 09:11:36 2014 +0000
+
+ This change needs to go to branch 9999
+
+ (cherry picked from commit 640f962733bfd2b9c44539a0d65952643750957e)
+
+# Looks good\&. Ship it!
+\fB$ git cl upload\fR
+# Wait for LGTM or TBR it\&.
+\fB$ git cl land\fR
+# Or skip the LGTM/TBR and just \*(Aqgit cl land \-\-bypass\-hooks\*(Aq
+.fi
+.if n \{\
+.RE
+.\}
+.sp
+.RE
.SH "SEE ALSO"
.sp
\fBgit-cherry-pick\fR(1), \fBgit-revert\fR(1)
diff --git a/man/src/common_demo_functions.sh b/man/src/common_demo_functions.sh
index 8f85ad1..7501f5a 100755
--- a/man/src/common_demo_functions.sh
+++ b/man/src/common_demo_functions.sh
@@ -50,6 +50,11 @@
echo "###COMMENT### $@"
}
+# run a command and print its output without printing the command itself
+output() {
+ "$@"
+}
+
# run a silent command
silent() {
if [[ $DEBUG ]]
diff --git a/man/src/git-drover.demo.1.sh b/man/src/git-drover.demo.1.sh
index 29a8cb0..9b12e38 100755
--- a/man/src/git-drover.demo.1.sh
+++ b/man/src/git-drover.demo.1.sh
@@ -3,25 +3,20 @@
drover_c "This change needs to go to branch 9999"
-echo "# Make sure we have the most up-to-date branch sources."
-run git fetch
-echo
echo "# Here's a commit (from some.committer) that we want to 'drover'."
run git log -n 1 --pretty=fuller
echo
-echo "# Checkout the branch we want to 'drover' to."
-run git checkout -b drover_9999 branch-heads/9999
-echo
echo "# Now do the 'drover'."
-echo "# IMPORTANT!!! Do Not leave off the '-x' flag"
-run git cherry-pick -x $(git show-ref -s pick_commit)
+pcommand git drover --branch 9999 \
+ --cherry-pick $(git show-ref -s pick_commit)
+
+echo "Going to cherry-pick"
+echo '"""'
+output git log -n 1
+echo '"""'
+echo "to 9999. Continue (y/n)? y"
echo
-echo "# That took the code authored by some.committer and committed it to"
-echo "# the branch by the person who drovered it (i.e. you)."
-run git log -n 1 --pretty=fuller
+echo "# A cl is uploaded to rietveld, where it can be reviewed before landing."
echo
-echo "# Looks good. Ship it!"
-pcommand git cl upload
-echo "# Wait for LGTM or TBR it."
-run git cl land
-echo "# Or skip the LGTM/TBR and just 'git cl land --bypass-hooks'"
+echo "About to land on 9999. Continue (y/n)? y"
+echo "# The cherry-pick cl is landed on the branch 9999."
diff --git a/man/src/git-drover.demo.3.sh b/man/src/git-drover.demo.3.sh
new file mode 100755
index 0000000..29a8cb0
--- /dev/null
+++ b/man/src/git-drover.demo.3.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+. git-drover.demo.common.sh
+
+drover_c "This change needs to go to branch 9999"
+
+echo "# Make sure we have the most up-to-date branch sources."
+run git fetch
+echo
+echo "# Here's a commit (from some.committer) that we want to 'drover'."
+run git log -n 1 --pretty=fuller
+echo
+echo "# Checkout the branch we want to 'drover' to."
+run git checkout -b drover_9999 branch-heads/9999
+echo
+echo "# Now do the 'drover'."
+echo "# IMPORTANT!!! Do Not leave off the '-x' flag"
+run git cherry-pick -x $(git show-ref -s pick_commit)
+echo
+echo "# That took the code authored by some.committer and committed it to"
+echo "# the branch by the person who drovered it (i.e. you)."
+run git log -n 1 --pretty=fuller
+echo
+echo "# Looks good. Ship it!"
+pcommand git cl upload
+echo "# Wait for LGTM or TBR it."
+run git cl land
+echo "# Or skip the LGTM/TBR and just 'git cl land --bypass-hooks'"
diff --git a/man/src/git-drover.txt b/man/src/git-drover.txt
index 2dde582..98eb92d 100644
--- a/man/src/git-drover.txt
+++ b/man/src/git-drover.txt
@@ -9,13 +9,41 @@
SYNOPSIS
--------
[verse]
-'git drover'
+'git drover' --branch <branch> --cherry-pick <commit>
+ [--parent_checkout <path-to-existing-checkout>]
+ [--verbose] [--dry-run]
DESCRIPTION
-----------
-`git drover` is NOT IMPLEMENTED yet. See the EXAMPLE section for the equivalent
-sequence of commands to run.
+`git drover` applies a commit to a release branch. It creates a new workdir from
+an existing checkout to avoid downloading a new checkout without affecting the
+existing checkout. Creating a workdir requires symlinks so this does not work on
+Windows. See the EXAMPLE section for the equivalent sequence of commands to run.
+
+`git drover` does not support reverts. See the EXAMPLE section for the
+equivalent sequence of commands to run.
+
+OPTIONS
+-------
+--branch <branch>::
+ The branch to cherry-pick the commit to.
+
+--cherry-pick <commit>::
+ The commit to cherry-pick.
+
+--parent_checkout::
+ The path to the chromium checkout to use as the source for a creating
+ git-new-workdir workdir to use for cherry-picking. If unspecified, the current
+ directory is used.
+
+-v::
+--verbose::
+ Enable verbose logging.
+
+--dry-run::
+ Skip landing the cherry-pick. Just ensure that the commit can be cherry-picked
+ into the branch.
EXAMPLE
-------
@@ -39,6 +67,10 @@
^^^^^^^^^^^^^^
demo:2[]
+Manual Merge Example
+^^^^^^^^^^^^^^^^^^^^
+demo:3[]
+
SEE ALSO
--------
linkgit:git-cherry-pick[1], linkgit:git-revert[1]
diff --git a/tests/git_common_test.py b/tests/git_common_test.py
index e7ec3b4..ef411ce 100755
--- a/tests/git_common_test.py
+++ b/tests/git_common_test.py
@@ -8,6 +8,7 @@
import binascii
import collections
import os
+import shutil
import signal
import sys
import tempfile
@@ -743,6 +744,35 @@
self.repo.run(inner)
+class GitMakeWorkdir(git_test_utils.GitRepoReadOnlyTestBase, GitCommonTestBase):
+ def setUp(self):
+ self._tempdir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self._tempdir)
+
+ REPO_SCHEMA = """
+ A
+ """
+
+ def testMakeWorkdir(self):
+ if not hasattr(os, 'symlink'):
+ return
+
+ workdir = os.path.join(self._tempdir, 'workdir')
+ self.gc.make_workdir(os.path.join(self.repo.repo_path, '.git'),
+ os.path.join(workdir, '.git'))
+ EXPECTED_LINKS = [
+ 'config', 'info', 'hooks', 'logs/refs', 'objects', 'refs',
+ ]
+ for path in EXPECTED_LINKS:
+ self.assertTrue(os.path.islink(os.path.join(workdir, '.git', path)))
+ self.assertEqual(os.path.realpath(os.path.join(workdir, '.git', path)),
+ os.path.join(self.repo.repo_path, '.git', path))
+ self.assertFalse(os.path.islink(os.path.join(workdir, '.git', 'HEAD')))
+
+
+
if __name__ == '__main__':
sys.exit(coverage_utils.covered_main(
os.path.join(DEPOT_TOOLS_ROOT, 'git_common.py')))
diff --git a/tests/git_drover_test.py b/tests/git_drover_test.py
new file mode 100755
index 0000000..c9f1f8f
--- /dev/null
+++ b/tests/git_drover_test.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Tests for git_drover."""
+
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import unittest
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from testing_support import auto_stub
+import git_drover
+
+
+class GitDroverTest(auto_stub.TestCase):
+
+ def setUp(self):
+ super(GitDroverTest, self).setUp()
+ self._temp_directory = tempfile.mkdtemp()
+ self._parent_repo = os.path.join(self._temp_directory, 'parent_repo')
+ self._target_repo = os.path.join(self._temp_directory, 'drover_branch_123')
+ os.makedirs(os.path.join(self._parent_repo, '.git'))
+ with open(os.path.join(self._parent_repo, '.git', 'config'), 'w') as f:
+ f.write('config')
+ with open(os.path.join(self._parent_repo, '.git', 'HEAD'), 'w') as f:
+ f.write('HEAD')
+ os.mkdir(os.path.join(self._parent_repo, '.git', 'info'))
+ with open(os.path.join(self._parent_repo, '.git', 'info', 'refs'),
+ 'w') as f:
+ f.write('refs')
+ self.mock(tempfile, 'mkdtemp', self._mkdtemp)
+ self.mock(__builtins__, 'raw_input', self._get_input)
+ self.mock(subprocess, 'check_call', self._check_call)
+ self.mock(subprocess, 'check_output', self._check_call)
+ self._commands = []
+ self._input = []
+ self._fail_on_command = None
+ self._has_os_symlink = hasattr(os, 'symlink')
+ if not self._has_os_symlink:
+ os.symlink = lambda source, dest: None
+
+ self.REPO_CHECK_COMMANDS = [
+ (['git', '--help'], self._parent_repo),
+ (['git', 'status'], self._parent_repo),
+ (['git', 'fetch', 'origin'], self._parent_repo),
+ (['git', 'rev-parse', 'refs/remotes/branch-heads/branch^{commit}'],
+ self._parent_repo),
+ (['git', 'rev-parse', 'cl^{commit}'], self._parent_repo),
+ (['git', 'show', '-s', 'cl'], self._parent_repo),
+ ]
+ self.LOCAL_REPO_COMMANDS = [
+ (['git', 'rev-parse', '--git-dir'], self._parent_repo),
+ (['git', 'config', 'core.sparsecheckout', 'true'], self._target_repo),
+ (['git', 'checkout', '-b', 'drover_branch_123',
+ 'refs/remotes/branch-heads/branch'], self._target_repo),
+ (['git', 'cherry-pick', '-x', 'cl'], self._target_repo),
+ (['git', 'reset', '--hard'], self._target_repo),
+ ]
+ self.UPLOAD_COMMAND = [(['git', 'cl', 'upload'], self._target_repo)]
+ self.LAND_COMMAND = [
+ (['git', 'cl', 'land', '--bypass-hooks'], self._target_repo),
+ ]
+ self.BRANCH_CLEANUP_COMMANDS = [
+ (['git', 'cherry-pick', '--abort'], self._target_repo),
+ (['git', 'checkout', '--detach'], self._target_repo),
+ (['git', 'branch', '-D', 'drover_branch_123'], self._target_repo),
+ ]
+
+ def tearDown(self):
+ shutil.rmtree(self._temp_directory)
+ if not self._has_os_symlink:
+ del os.symlink
+ super(GitDroverTest, self).tearDown()
+
+ def _mkdtemp(self, prefix='tmp'):
+ self.assertEqual('drover_branch_', prefix)
+ os.mkdir(self._target_repo)
+ return self._target_repo
+
+ def _get_input(self, message):
+ result = self._input.pop(0)
+ if result == 'EOF':
+ raise EOFError
+ return result
+
+ def _check_call(self, args, stderr=None, stdout=None, shell='', cwd=None):
+ self.assertFalse(shell)
+ self._commands.append((args, cwd))
+ if (self._fail_on_command is not None and
+ self._fail_on_command == len(self._commands)):
+ raise subprocess.CalledProcessError(1, args[0])
+ if args == ['git', 'rev-parse', '--git-dir']:
+ return os.path.join(self._parent_repo, '.git')
+
+ def testSuccess(self):
+ self._input = ['y', 'y']
+ git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
+ self.assertEqual(
+ self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
+ self.UPLOAD_COMMAND + self.LAND_COMMAND + self.BRANCH_CLEANUP_COMMANDS,
+ self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testDryRun(self):
+ self._input = ['y']
+ git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, True)
+ self.assertEqual(
+ self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
+ self.BRANCH_CLEANUP_COMMANDS, self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testCancelEarly(self):
+ self._input = ['n']
+ git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
+ self.assertEqual(self.REPO_CHECK_COMMANDS, self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testEOFOnConfirm(self):
+ self._input = ['EOF']
+ git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
+ self.assertEqual(self.REPO_CHECK_COMMANDS, self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testCancelLate(self):
+ self._input = ['y', 'n']
+ git_drover.cherry_pick_change('branch', 'cl', self._parent_repo, False)
+ self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
+ self.UPLOAD_COMMAND + self.BRANCH_CLEANUP_COMMANDS,
+ self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testFailDuringCheck(self):
+ self._input = []
+ self._fail_on_command = 1
+ self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
+ 'cl', self._parent_repo, False)
+ self.assertEqual(self.REPO_CHECK_COMMANDS[:1], self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testFailDuringBranchCreation(self):
+ self._input = ['y']
+ self._fail_on_command = 8
+ self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
+ 'cl', self._parent_repo, False)
+ self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS[:2],
+ self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testFailDuringCherryPick(self):
+ self._input = ['y']
+ self._fail_on_command = 10
+ self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
+ 'cl', self._parent_repo, False)
+ self.assertEqual(
+ self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS[:4] +
+ self.BRANCH_CLEANUP_COMMANDS, self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testFailAfterCherryPick(self):
+ self._input = ['y']
+ self._fail_on_command = 11
+ self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
+ 'cl', self._parent_repo, False)
+ self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
+ self.BRANCH_CLEANUP_COMMANDS, self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testFailOnUpload(self):
+ self._input = ['y']
+ self._fail_on_command = 12
+ self.assertRaises(git_drover.Error, git_drover.cherry_pick_change, 'branch',
+ 'cl', self._parent_repo, False)
+ self.assertEqual(self.REPO_CHECK_COMMANDS + self.LOCAL_REPO_COMMANDS +
+ self.UPLOAD_COMMAND + self.BRANCH_CLEANUP_COMMANDS,
+ self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+ def testInvalidParentRepoDirectory(self):
+ self.assertRaises(
+ git_drover.Error, git_drover.cherry_pick_change, 'branch', 'cl',
+ os.path.join(self._parent_repo, 'fake'), False)
+ self.assertFalse(self._commands)
+ self.assertFalse(os.path.exists(self._target_repo))
+ self.assertFalse(self._input)
+
+
+if __name__ == '__main__':
+ unittest.main()