<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -9,13 +9,13 @@ from fabric.contrib.project import rsync_project
 import fabric.version
 
 
-def test():
+def test(args=None):
     &quot;&quot;&quot;
     Run all unit tests and doctests.
     &quot;&quot;&quot;
-    # Need show_stderr=True because the interesting output of nosetests is
-    # actually sent to stderr, not stdout.
-    print(local('nosetests -sv --with-doctest', capture=False))
+    if args is None:
+        args = &quot;&quot;
+    print(local('nosetests -sv --with-doctest %s' % args, capture=False))
 
 
 def build_docs(clean='no'):</diff>
      <filename>fabfile.py</filename>
    </modified>
    <modified>
      <diff>@@ -6,7 +6,7 @@ anything else inside the package -- like, say, the version number used in
 setup.py -- without triggering loads of most of the code. Which doesn't work so
 well when you're using setup.py to install e.g. paramiko!
 &quot;&quot;&quot;
-from fabric.context_managers import cd, hide, settings, show
+from fabric.context_managers import cd, hide, settings, show, path
 from fabric.decorators import hosts, roles, runs_once
 from fabric.operations import require, prompt, put, get, run, sudo, local
 from fabric.state import env, output</diff>
      <filename>fabric/api.py</filename>
    </modified>
    <modified>
      <diff>@@ -180,3 +180,32 @@ def cd(path):
     else:
         new_cwd = path
     return _setenv(cwd=new_cwd)
+
+
+def path(path, behavior='append'):
+    &quot;&quot;&quot;
+    Append the given ``path`` to the PATH used to execute any wrapped commands.
+
+    Any calls to `run` or `sudo` within the wrapped block will implicitly have
+    a string similar to ``&quot;PATH=$PATH:&lt;path&gt; &quot;`` prepended before the given
+    command.
+
+    You may customize the behavior of `path` by specifying the optional
+    ``behavior`` keyword argument, as follows:
+
+    * ``'append'``: append given path to the current ``$PATH``, e.g.
+      ``PATH=$PATH:&lt;path&gt;``. This is the default behavior.
+    * ``'prepend'``: prepend given path to the current ``$PATH``, e.g.
+      ``PATH=&lt;path&gt;:$PATH``.
+    * ``'replace'``: ignore previous value of ``$PATH`` altogether, e.g.
+      ``PATH=&lt;path&gt;``.
+
+    .. note::
+
+        This context manager is currently implemented by modifying (and, as
+        always, restoring afterwards) the current value of environment
+        variables, ``env.path`` and ``env.path_behavior``. However, this
+        implementation may change in the future, so we do not recommend
+        manually altering them directly.
+    &quot;&quot;&quot;
+    return _setenv(path=path, path_behavior=behavior)</diff>
      <filename>fabric/context_managers.py</filename>
    </modified>
    <modified>
      <diff>@@ -372,56 +372,109 @@ def get(remote_path, local_path):
             _handle_failure(message=msg % remote_path, exception=e)
 
 
-@needs_host
-def run(command, shell=True, pty=False):
+
+
+def _sudo_prefix(user):
     &quot;&quot;&quot;
-    Run a shell command on a remote host.
+    Return ``env.sudo_prefix`` with ``user`` inserted if necessary.
+    &quot;&quot;&quot;
+    # Insert env.sudo_prompt into env.sudo_prefix
+    prefix = env.sudo_prefix % env.sudo_prompt
+    if user is not None:
+        if str(user).isdigit():
+            user = &quot;#%s&quot; % user
+        return &quot;%s -u \&quot;%s\&quot; &quot; % (prefix, user)
+    return prefix
 
-    If ``shell`` is True (the default), ``run()`` will execute the given
-    command string via a shell interpreter, the value of which may be
-    controlled by setting ``env.shell`` (defaulting to something similar to
-    ``/bin/bash -l -c &quot;&lt;command&gt;&quot;``.) Any double-quote (``&quot;``) characters in
-    ``command`` will be automatically escaped when ``shell`` is True.
 
-    `run` will return the result of the remote program's stdout as a
-    single (likely multiline) string. This string will exhibit a ``failed``
-    boolean attribute specifying whether the command failed or succeeded, and
-    will also include the return code as the ``return_code`` attribute.
+def _shell_wrap(command, shell=True, sudo_prefix=None):
+    &quot;&quot;&quot;
+    Conditionally wrap given command in env.shell (while honoring sudo.)
+    &quot;&quot;&quot;
+    # Do shell escaping here and only here.
+    command = _shell_escape(command)
+    # Sudo plus space, or empty string
+    if sudo_prefix is None:
+        sudo_prefix = &quot;&quot;
+    else:
+        sudo_prefix += &quot; &quot;
+    # Shell plus space, or empty string; and command quoted or not.
+    if shell:
+        shell = env.shell + &quot; &quot;
+        command = '&quot;%s&quot;' % command
+    else:
+        shell = &quot;&quot;
+    # Resulting string should now have correct formatting
+    return sudo_prefix + shell + command
 
-    Standard error will also be attached, as a string, to this return value as
-    the ``stderr`` attribute.
 
-    You may pass ``pty=True`` to force allocation of a pseudo tty on
-    the remote end. This is not normally required, but some programs may
-    complain (or, even more rarely, refuse to run) if a tty is not present.
+def _prefix_commands(command):
+    &quot;&quot;&quot;
+    Prefixes ``command`` with any &quot;prefix commands&quot;, e.g. ``cd &lt;foo&gt; &amp;&amp; ``.
 
-    Examples::
-    
-        run(&quot;ls /var/www/&quot;)
-        run(&quot;ls /home/myuser&quot;, shell=False)
-        output = run('ls /var/www/site1')
-    
+    Currently, this only applies the directory switching implemented in
+    `~fabric.context_managers.cd`.
+    &quot;&quot;&quot;
+    # cd(): &quot;cd&quot; call bringing us to the current working dir
+    cwd = env.cwd
+    if cwd:
+        cwd = 'cd \&quot;%s\&quot; &amp;&amp; ' % cwd
+    else:
+        cwd = ''
+    return cwd + command
+
+
+def _prefix_env_vars(command):
+    &quot;&quot;&quot;
+    Prefixes ``command`` with any shell environment vars, e.g. ``PATH=foo ``.
+
+    Currently, this only applies the PATH updating implemented in
+    `~fabric.context_managers.path`.
+    &quot;&quot;&quot;
+    # path(): local shell env var update, appending/prepending/replacing $PATH
+    path = env.path
+    if path:
+        if env.path_behavior == 'append':
+            path = 'PATH=$PATH:\&quot;%s\&quot; ' % path
+        elif env.path_behavior == 'prepend':
+            path = 'PATH=\&quot;%s\&quot;:$PATH ' % path
+        elif env.path_behavior == 'replace':
+            path = 'PATH=\&quot;%s\&quot; ' % path
+    else:
+        path = ''
+    return path + command
+
+
+def _execute_remotely(command, sudo=False, shell=True, pty=False, user=None):
+    &quot;&quot;&quot;
+    Execute remote shell command, either &quot;normally&quot; or via ``sudo``.
+
+    Used to drive `~fabric.operations.run` and `~fabric.operations.sudo`.
+    &quot;&quot;&quot;
+
+
+def _run_command(command, shell=True, pty=False, sudo=False, user=None):
+    &quot;&quot;&quot;
+    Underpinnings of `run` and `sudo`. See their docstrings for more info.
     &quot;&quot;&quot;
     # Set up new var so original argument can be displayed verbatim later.
-    real_command = command
-    if shell:
-        # Handle cwd munging via 'cd' context manager
-        cwd = env.get('cwd', '')
-        if cwd:
-            cwd = 'cd \&quot;%s\&quot; &amp;&amp; ' % _shell_escape(cwd)
-        # Construct final real, full command
-        real_command = '%s &quot;%s&quot;' % (env.shell,
-            _shell_escape(cwd + real_command))
+    given_command = command
+    # Handle context manager modifications, and shell wrapping
+    wrapped_command = _shell_wrap(
+        _prefix_commands(_prefix_env_vars(command)),
+        shell,
+        _sudo_prefix(user) if sudo else None
+    )
     if output.debug:
-        print(&quot;[%s] run: %s&quot; % (env.host_string, real_command))
+        print(&quot;[%s] run: %s&quot; % (env.host_string, wrapped_command))
     elif output.running:
-        print(&quot;[%s] run: %s&quot; % (env.host_string, command))
+        print(&quot;[%s] run: %s&quot; % (env.host_string, given_command))
     channel = connections[env.host_string]._transport.open_session()
     # Create pty if necessary (using Paramiko default options, which as of
     # 1.7.4 is vt100 $TERM @ 80x24 characters)
     if pty:
         channel.get_pty()
-    channel.exec_command(real_command)
+    channel.exec_command(wrapped_command)
     capture_stdout = []
     capture_stderr = []
 
@@ -448,7 +501,7 @@ def run(command, shell=True, pty=False):
     out.failed = False
     if status != 0:
         out.failed = True
-        msg = &quot;run() encountered an error (return code %s) while executing '%s'&quot; % (status, command)
+        msg = &quot;%s() encountered an error (return code %s) while executing '%s'&quot; % ('sudo' if sudo else 'run', status, command)
         _handle_failure(message=msg)
 
     # Attach return code to output string so users who have set things to warn
@@ -462,6 +515,39 @@ def run(command, shell=True, pty=False):
 
 
 @needs_host
+def run(command, shell=True, pty=False):
+    &quot;&quot;&quot;
+    Run a shell command on a remote host.
+
+    If ``shell`` is True (the default), ``run()`` will execute the given
+    command string via a shell interpreter, the value of which may be
+    controlled by setting ``env.shell`` (defaulting to something similar to
+    ``/bin/bash -l -c &quot;&lt;command&gt;&quot;``.) Any double-quote (``&quot;``) characters in
+    ``command`` will be automatically escaped when ``shell`` is True.
+
+    `run` will return the result of the remote program's stdout as a
+    single (likely multiline) string. This string will exhibit a ``failed``
+    boolean attribute specifying whether the command failed or succeeded, and
+    will also include the return code as the ``return_code`` attribute.
+
+    Standard error will also be attached, as a string, to this return value as
+    the ``stderr`` attribute.
+
+    You may pass ``pty=True`` to force allocation of a pseudo tty on
+    the remote end. This is not normally required, but some programs may
+    complain (or, even more rarely, refuse to run) if a tty is not present.
+
+    Examples::
+    
+        run(&quot;ls /var/www/&quot;)
+        run(&quot;ls /home/myuser&quot;, shell=False)
+        output = run('ls /var/www/site1')
+    
+    &quot;&quot;&quot;
+    return _run_command(command, shell, pty)
+
+
+@needs_host
 def sudo(command, shell=True, user=None, pty=False):
     &quot;&quot;&quot;
     Run a shell command on a remote host, with superuser privileges.
@@ -499,75 +585,7 @@ def sudo(command, shell=True, user=None, pty=False):
         result = sudo(&quot;ls /tmp/&quot;)
     
     &quot;&quot;&quot;
-    # Construct sudo command, with user if necessary
-    if user is not None:
-        if str(user).isdigit():
-            user = &quot;#%s&quot; % user
-        sudo_prefix = &quot;sudo -S -p '%%s' -u \&quot;%s\&quot; &quot; % user
-    else:
-        sudo_prefix = &quot;sudo -S -p '%s' &quot;
-    # Put in explicit sudo prompt string (so we know what to look for when
-    # detecting prompts)
-    sudo_prefix = sudo_prefix % env.sudo_prompt
-    # Without using a shell, we just do 'sudo -u blah my_command'
-    if (not env.use_shell) or (not shell):
-        real_command = &quot;%s %s&quot; % (sudo_prefix, _shell_escape(command))
-    # With a shell, we do 'sudo -u blah /bin/bash -l -c &quot;my_command&quot;'
-    else:
-        # With a shell, we can also honor cwd
-        cwd = env.get('cwd', '')
-        if cwd:
-            cwd = 'cd \&quot;%s\&quot; &amp;&amp; ' % _shell_escape(cwd)
-        real_command = '%s %s &quot;%s&quot;' % (sudo_prefix, env.shell,
-            _shell_escape(cwd + command))
-    if output.debug:
-        print(&quot;[%s] sudo: %s&quot; % (env.host_string, real_command))
-    elif output.running:
-        print(&quot;[%s] sudo: %s&quot; % (env.host_string, command))
-    channel = connections[env.host_string]._transport.open_session()
-    # Create pty if necessary (using Paramiko default options, which as of
-    # 1.7.4 is vt100 $TERM @ 80x24 characters)
-    if pty:
-        channel.get_pty()
-    # Execute
-    channel.exec_command(real_command)
-    capture_stdout = []
-    capture_stderr = []
-
-    out_thread = output_thread(&quot;[%s] out&quot; % env.host_string, channel,
-        capture=capture_stdout)
-    err_thread = output_thread(&quot;[%s] err&quot; % env.host_string, channel,
-        stderr=True, capture=capture_stderr)
-
-    # Close channel when done
-    status = channel.recv_exit_status()
-
-    # Wait for threads to exit before returning (otherwise we will occasionally
-    # end up returning before the threads have fully wrapped up)
-    out_thread.join()
-    err_thread.join()
-
-    # Close channel
-    channel.close()
-
-    # Assemble stdout string
-    out = _AttributeString(&quot;&quot;.join(capture_stdout).strip())
-    err = _AttributeString(&quot;&quot;.join(capture_stderr).strip())
-
-    # Error handling
-    out.failed = False
-    if status != 0:
-        out.failed = True
-        msg = &quot;sudo() encountered an error (return code %s) while executing '%s'&quot; % (status, command)
-        _handle_failure(message=msg)
-
-    # Attach return code for convenience
-    out.return_code = status
-
-    # Attach stderr for those who need it.
-    out.stderr = err
-
-    return out
+    return _run_command(command, shell, pty, sudo=True, user=user) 
 
 
 def local(command, capture=True):
@@ -598,16 +616,13 @@ def local(command, capture=True):
     ``output.stderr`` will be used to determine what is printed and what is
     discarded.
     &quot;&quot;&quot;
-    # Handle cd() context manager
-    cwd = env.get('cwd', '')
-    if cwd:
-        cwd = 'cd %s &amp;&amp; ' % _shell_escape(cwd)
-    # Construct real command
-    real_command = cwd + command
+    given_command = command
+    # Apply cd(), path() etc
+    wrapped_command = _prefix_commands(_prefix_env_vars(command))
     if output.debug:
-        print(&quot;[localhost] run: %s&quot; % (real_command))
+        print(&quot;[localhost] run: %s&quot; % (wrapped_command))
     elif output.running:
-        print(&quot;[localhost] run: &quot; + command)
+        print(&quot;[localhost] run: &quot; + given_command)
     # By default, capture both stdout and stderr
     PIPE = subprocess.PIPE
     out_stream = PIPE
@@ -619,7 +634,7 @@ def local(command, capture=True):
             out_stream = None
         if output.stderr:
             err_stream = None
-    p = subprocess.Popen([real_command], shell=True, stdout=out_stream,
+    p = subprocess.Popen([wrapped_command], shell=True, stdout=out_stream,
             stderr=err_stream)
     (stdout, stderr) = p.communicate()
     # Handle error condition (deal with stdout being None, too)</diff>
      <filename>fabric/operations.py</filename>
    </modified>
    <modified>
      <diff>@@ -207,10 +207,15 @@ env = _AttributeDict({
     # Version number for --version
     'version': get_version(),
     'sudo_prompt': 'sudo password:',
+    # -S so sudo accepts passwd via stdin, -p with our known-value prompt for
+    # later detection (thus %s -- gets filled with env.sudo_prompt at runtime)
+    'sudo_prefix': &quot;sudo -S -p '%s' &quot;,
     'again_prompt': 'Sorry, try again.\n',
     'use_shell': True,
     'roledefs': {},
-    'cwd': ''
+    'cwd': '',
+    'path': '',
+    'path_behavior': 'append',
 })
 
 # Add in option defaults</diff>
      <filename>fabric/state.py</filename>
    </modified>
    <modified>
      <diff>@@ -5,7 +5,9 @@ import sys
 from nose.tools import raises, eq_
 from fudge import with_patched_object
 
-from fabric.operations import require, prompt
+from fabric.state import env
+from fabric.operations import require, prompt, _sudo_prefix, _shell_wrap, \
+    _shell_escape
 from utils import mock_streams
 
 
@@ -96,3 +98,66 @@ def test_prompt_with_default():
     d = &quot;default!&quot;
     prompt(s, default=d)
     eq_(sys.stdout.getvalue(), &quot;%s [%s] &quot; % (s, d))
+    
+
+#
+# run()/sudo()
+#
+
+def test_sudo_prefix_with_user():
+    &quot;&quot;&quot;
+    _sudo_prefix() returns prefix plus -u flag for nonempty user
+    &quot;&quot;&quot;
+    eq_(
+        _sudo_prefix(user=&quot;foo&quot;),
+        &quot;%s -u \&quot;foo\&quot; &quot; % (env.sudo_prefix % env.sudo_prompt)
+    )
+
+
+def test_sudo_prefix_without_user():
+    &quot;&quot;&quot;
+    _sudo_prefix() returns standard prefix when user is empty
+    &quot;&quot;&quot;
+    eq_(_sudo_prefix(user=None), env.sudo_prefix % env.sudo_prompt)
+
+
+def test_shell_wrap():
+    prefix = &quot;prefix&quot;
+    command = &quot;command&quot;
+    for description, shell, sudo_prefix, result in (
+        (&quot;shell=True, sudo_prefix=None&quot;,
+            True, None, &quot;%s \&quot;%s\&quot;&quot; % (env.shell, command)),
+        (&quot;shell=True, sudo_prefix=string&quot;,
+            True, prefix, prefix + &quot; %s \&quot;%s\&quot;&quot; % (env.shell, command)),
+        (&quot;shell=False, sudo_prefix=None&quot;,
+            False, None, command),
+        (&quot;shell=False, sudo_prefix=string&quot;,
+            False, prefix, prefix + &quot; &quot; + command),
+    ):
+        eq_.description = &quot;_shell_wrap: %s&quot; % description
+        yield eq_, _shell_wrap(command, shell, sudo_prefix), result
+        del eq_.description
+
+
+def test_shell_wrap_escapes_command():
+    &quot;&quot;&quot;
+    _shell_wrap() escapes given command
+    &quot;&quot;&quot;
+    cmd = &quot;cd \&quot;Application Support\&quot;&quot;
+    eq_(_shell_wrap(cmd, shell=False), _shell_escape(cmd))
+
+
+def test_shell_escape_escapes_doublequotes():
+    &quot;&quot;&quot;
+    _shell_escape() escapes double-quotes
+    &quot;&quot;&quot;
+    cmd = &quot;cd \&quot;Application Support\&quot;&quot;
+    eq_(_shell_escape(cmd), 'cd \\&quot;Application Support\\&quot;')
+
+
+def test_shell_escape_escapes_dollar_signs():
+    &quot;&quot;&quot;
+    _shell_escape() escapes dollar signs
+    &quot;&quot;&quot;
+    cmd = &quot;cd $HOME&quot;
+    eq_(_shell_escape(cmd), 'cd \$HOME')</diff>
      <filename>tests/test_operations.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>d4ad5859aa6d8c1f564718f79150c714f724bc6a</id>
    </parent>
  </parents>
  <author>
    <name>Jeff Forcier</name>
    <email>jeff@bitprophet.org</email>
  </author>
  <url>http://github.com/karmazilla/fabric/commit/ce58a1b0fe6477fd498263b058b5d6f2cf1ce3ca</url>
  <id>ce58a1b0fe6477fd498263b058b5d6f2cf1ce3ca</id>
  <committed-date>2009-09-17T07:56:06-07:00</committed-date>
  <authored-date>2009-09-17T07:56:06-07:00</authored-date>
  <message>Squashed commit of the following:

commit 20f6885527d6b38d350f5a50872ab10faa97fb9e
Author: Jeff Forcier &lt;jeff@bitprophet.org&gt;
Date:   Thu Sep 17 10:33:51 2009 -0400

    Apply new subroutines to local().

commit 4bd081c5e542e0ed64c21b07f9b8f405e10fc7d0
Author: Jeff Forcier &lt;jeff@bitprophet.org&gt;
Date:   Thu Sep 17 10:31:39 2009 -0400

    Fix failing sudo prefix tests

commit 7ae4c6c9f4babb2e18d18ff3b51e4e95e00a1441
Author: Jeff Forcier &lt;jeff@bitprophet.org&gt;
Date:   Wed Sep 16 18:23:06 2009 -0400

    First draft of run/sudo refactor complete.

    * definitely need to reorganize operations.py now, so many subroutines!
    * local() needs a partial treatment to get cd() and path() working

commit 03bbd43e4a8566f4cdae5f11532374c80291fbd7
Author: Jeff Forcier &lt;jeff@bitprophet.org&gt;
Date:   Tue Sep 15 17:21:04 2009 -0400

    path implemented, in middle of run/sudo refactor

    also added args to fabfile 'test' task</message>
  <tree>ddd147197df33292787d2d8de5f862988690697b</tree>
  <committer>
    <name>Jeff Forcier</name>
    <email>jeff@bitprophet.org</email>
  </committer>
</commit>
