Skip to content

Commit

Permalink
completely remove sh.cd
Browse files Browse the repository at this point in the history
  • Loading branch information
amoffat committed Feb 8, 2023
1 parent d1665dc commit f3073be
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 50 deletions.
33 changes: 27 additions & 6 deletions MIGRATION.md
@@ -1,16 +1,31 @@
# Migrating from 1.* to 2.*
# Migrating from 1._ to 2._

This document provides an upgrade path from `1.*` to `2.*`.

## `sh.cd` builtin removed

There is no `sh.cd` command anymore. It was always command implemented in sh, as
some systems provide it as a shell builtin, while others have an actual binary.
But neither of them persisted the directory change between other `sh` calls,
which is why it was implemented in sh.

### Workaround

If you were using `sh.cd(dir)`, use the context manager `with sh.pushd(dir)`
instead. All of the commands in the managed context will have the correct
directory.

## Return value now a true string

In `2.*`, the return value of an executed `sh` command has changed (in most cases) from
a `RunningCommand` object to a unicode string. This makes using the output of a command
more natural.

### Workaround
To continue returning a `RunningCommand` object, you must use the `_return_cmd=True`
special keyword argument. You can achieve this on each file with the following code at
the top of files that use `sh`:

To continue returning a `RunningCommand` object, you must use the `_return_cmd=True`
special keyword argument. You can achieve this on each file with the following code at
the top of files that use `sh`:

```python
import sh
Expand All @@ -19,22 +34,26 @@ sh = sh(_return_cmd=True)
```

## Piping to STDIN

Previously, if the first argument of a sh command was an instance of `RunningCommand`,
it was automatically fed into the process's STDIN. This is no longer the case and you
must explicitly use `_in=`.

### Workaround

None

## Removal of the custom `sh.cd`

`sh.cd` was implemented as a custom function and shadowed the system `cd` binary in
order to be useful. `sh.cd` changed the current working directory globally for the
python script. With the removal of this custom override, calling `sh.cd` will fall back
to your actual system binary, which will only affect the current working directory
*for the duration of the `sh.cd` process.* In other words, it will no longer behave
_for the duration of the `sh.cd` process._ In other words, it will no longer behave
as intended.

### Workaround

I have inserted a breaking `DeprecationWarning` on all uses of `sh.cd` to help you find
them quickly. Replace those instances with `sh.pushd`. It is like `sh.cd`, but operates
as a context manager with scoping to only affect `sh` commands within the scope:
Expand All @@ -47,13 +66,15 @@ with sh.pushd("/tmp"):
```

## New processes don't launch in new session

In `1.*`, `_new_session` defaulted to `True`. It now defaults to `False`. The reason
for this is that it makes more sense for launched processes to default to being in
the process group of the python script, so that they receive SIGINTs correctly.

### Workaround

```python
import sh

sh = sh(_new_session=True)
```
```
20 changes: 0 additions & 20 deletions sh.py
Expand Up @@ -3418,12 +3418,6 @@ def __getitem__(self, k):
if k.startswith("__") and k.endswith("__"):
raise AttributeError

if k == "cd":
# Don't resolve the system binary. It's useful in scripts to be
# able to switch directories in the current process. Can also be
# used as a context manager.
return Cd

# is it a command?
cmd = resolve_command(k, self.globs[Command.__name__], self.baked_args)
if cmd:
Expand Down Expand Up @@ -3454,20 +3448,6 @@ def b_which(program, paths=None):
return _which(program, paths)


class Cd(object):
def __new__(cls, path=None):
res = super(Cd, cls).__new__(cls)
res.old_path = os.getcwd()
os.chdir(path or os.path.expanduser("~"))
return res

def __enter__(self):
pass

def __exit__(self, exc_type, exc_val, exc_tb):
os.chdir(self.old_path)


class Contrib(ModuleType): # pragma: no cover
@classmethod
def __call__(cls, name):
Expand Down
26 changes: 2 additions & 24 deletions tests/test.py
Expand Up @@ -2579,35 +2579,16 @@ def test_pushd(self):
self.assertEqual(new_wd2, child)

def test_pushd_cd(self):
"""test that pushd works like pushd/popd with built-in cd correctly"""
"""test that pushd works like pushd/popd"""
child = realpath(tempfile.mkdtemp())
try:
old_wd = os.getcwd()
with sh.pushd(tempdir):
self.assertEqual(tempdir, os.getcwd())
sh.cd(child)
self.assertEqual(child, os.getcwd())

self.assertEqual(str(tempdir), os.getcwd())
self.assertEqual(old_wd, os.getcwd())
finally:
os.rmdir(child)

def test_cd_homedir(self):
orig = os.getcwd()
my_dir = os.path.realpath(
os.path.expanduser("~")
) # Use realpath because homedir may be a symlink
sh.cd()

self.assertNotEqual(orig, os.getcwd())
self.assertEqual(my_dir, os.getcwd())

def test_cd_context_manager(self):
orig = os.getcwd()
with sh.cd(tempdir):
self.assertEqual(tempdir, os.getcwd())
self.assertEqual(orig, os.getcwd())

def test_non_existant_cwd(self):
from sh import ls

Expand Down Expand Up @@ -3161,9 +3142,6 @@ def test_fd_over_1024(self):
def test_args_deprecated(self):
self.assertRaises(DeprecationWarning, sh.args, _env={})

def test_cd_deprecated(self):
self.assertRaises(DeprecationWarning, sh.cd)

def test_percent_doesnt_fail_logging(self):
"""test that a command name doesn't interfere with string formatting in
the internal loggers"""
Expand Down

0 comments on commit f3073be

Please sign in to comment.