Repository: iterative/dvc

Issue:
dvc add causes ERROR without saying that's the problem
# Bug Report

<!--
## Issue name

dvc add: causes error

Example: `repro: doesn't detect input changes`
-->

## Description
dvc add causes ERROR without saying that's the problem. 
We need clear explanation that is wrong
<!--
A clear and concise description of what the bug is.
-->

### Reproduce

(venv) (base) insaf@MacBook-Pro-Insaf test_gan % dvc add ../tabgan/Research/data/credit/credit.gz
Adding...                                                                                                                                                  
ERROR: /Users/insaf/Desktop/ml projects/tabgan/Research/data/credit/credit.gz
<!--
Example:

1. dvc init
2. Copy dataset.zip to the directory
3. dvc add dataset.zip
4. dvc run -d dataset.zip -o model ./train.sh
5. modify dataset.zip
6. dvc repro
-->

### Expected
Add file to dvc config file
<!--
A clear and concise description of what you expect to happen.
-->

### Environment information

<!--
This is required to ensure that we can reproduce the bug.
-->

**Output of `dvc doctor`:**

```console
$ dvc doctor
```
(venv) (base) insaf@MacBook-Pro-Insaf test_gan % dvc doctor
DVC version: 2.0.18 (osxpkg)
---------------------------------
Platform: Python 3.8.9 on macOS-10.16-x86_64-i386-64bit
Supports: All remotes
Cache types: <https://error.dvc.org/no-dvc-cache>
Caches: local
Remotes: None
Workspace directory: apfs on /dev/disk1s1s1
Repo: dvc, git

Please check https://github.com/iterative/dvc/wiki/Debugging-DVC on ways to gather more information regarding the issue.

If applicable, please also provide a `--verbose` output of the command, eg: `dvc add --verbose`.
If the issue is regarding the performance, please attach the profiling information and the benchmark comparisons.
-->



Discussion:
Hi @Diyago Please add `-v` to the command you are running and post full log for it.
```
(venv) (base) insaf@MacBook-Pro-Insaf test_gan % dvc doctor -v      
DVC version: 2.0.18 (osxpkg)
---------------------------------
Platform: Python 3.8.9 on macOS-10.16-x86_64-i386-64bit
Supports: All remotes
Cache types: <https://error.dvc.org/no-dvc-cache>
Caches: local
Remotes: None
Workspace directory: apfs on /dev/disk1s1s1
Repo: dvc, git
2021-04-26 00:29:35,091 DEBUG: Analytics is enabled.
2021-04-26 00:29:35,241 DEBUG: Trying to spawn '['daemon', '-q', 'analytics', '/var/folders/s7/khxd34ns2jxbbvl4tbcdcv5w0000gn/T/tmpq1x_cxax']'
2021-04-26 00:29:35,243 DEBUG: Spawned '['daemon', '-q', 'analytics', '/var/folders/s7/khxd34ns2jxbbvl4tbcdcv5w0000gn/T/tmpq1x_cxax']'
```
```
(venv) (base) insaf@MacBook-Pro-Insaf test_gan % dvc add ../tabgan/Research/data/credit/credit.gz --verbose
2021-04-26 00:30:46,405 DEBUG: Check for update is enabled.
Adding...                                                                   
2021-04-26 00:30:46,661 ERROR: /Users/insaf/Desktop/ml projects/tabgan/Research/data/credit/credit.gz
------------------------------------------------------------
Traceback (most recent call last):
  File "dvc/command/add.py", line 21, in run
  File "dvc/repo/__init__.py", line 49, in wrapper
  File "dvc/repo/scm_context.py", line 14, in run
  File "dvc/repo/add.py", line 123, in add
  File "dvc/repo/add.py", line 195, in _process_stages
  File "dvc/stage/__init__.py", line 437, in save
  File "dvc/stage/__init__.py", line 451, in save_outs
  File "dvc/output/base.py", line 289, in save
  File "dvc/output/base.py", line 269, in ignore
  File "dvc/scm/git/__init__.py", line 153, in ignore
  File "dvc/scm/git/__init__.py", line 148, in _get_gitignore
dvc.scm.base.FileNotInRepoError: /Users/insaf/Desktop/ml projects/tabgan/Research/data/credit/credit.gz
------------------------------------------------------------
2021-04-26 00:30:46,663 DEBUG: Analytics is enabled.
2021-04-26 00:30:46,741 DEBUG: Trying to spawn '['daemon', '-q', 'analytics', '/var/folders/s7/khxd34ns2jxbbvl4tbcdcv5w0000gn/T/tmpd8btdh0j']'
2021-04-26 00:30:46,743 DEBUG: Spawned '['daemon', '-q', 'analytics', '/var/folders/s7/khxd34ns2jxbbvl4tbcdcv5w0000gn/T/tmpd8btdh0j']'
```

@Diyago Thanks! Ok, so the issue is that the file you are trying to add is outside of your dvc project. Could you elaborate on what you are trying to do?

We should improve the error message for that exception, that's for sure.
The file outside of your dvc project - yes, that's right. But is the problem of doing? 

Im trying to add file to track for the dvc 
@Diyago Yeah, `dvc add` is similar to `git add` in that it expects you to add files/dirs that are inside of your project. You could copy/move it into your repo and then

```
dvc add credit.gz
```

or you could tell dvc that you want it to take that file and put it as `credit.gz` into your repo, e.g.:

```
dvc add ../tabgan/Research/data/credit/credit.gz -o credit.gz
```

Btw, be sure to check out our get-started guide https://dvc.org/doc/start if you haven't already.
Maybe we can make a more friendly message.
@Diyago Did one of those commands work for you?

Keeping this issue open for error message fix.

For the record: it should probably have been caught by our `check_no_externals` check.

Resolving patch:
diff --git a/dvc/output/local.py b/dvc/output/local.py
--- a/dvc/output/local.py
+++ b/dvc/output/local.py
@@ -48,7 +48,11 @@ def _parse_path(self, fs, path):
         return self.FS_CLS.PATH_CLS(abs_p)
 
     def __str__(self):
-        if not self.repo or not self.is_in_repo:
+        if (
+            not self.repo
+            or urlparse(self.def_path).scheme == "remote"
+            or os.path.isabs(self.def_path)
+        ):
             return str(self.def_path)
 
         cur_dir = os.getcwd()
@@ -63,8 +67,15 @@ def fspath(self):
 
     @property
     def is_in_repo(self):
-        def_scheme = urlparse(self.def_path).scheme
-        return def_scheme != "remote" and not os.path.isabs(self.def_path)
+        if urlparse(self.def_path).scheme == "remote":
+            return False
+
+        if os.path.isabs(self.def_path):
+            return False
+
+        if self.repo and not self.path_info.isin(self.repo.root_dir):
+            return False
+        return True
 
     def dumpd(self):
         ret = super().dumpd()
diff --git a/dvc/scm/git/__init__.py b/dvc/scm/git/__init__.py
--- a/dvc/scm/git/__init__.py
+++ b/dvc/scm/git/__init__.py
@@ -151,7 +151,9 @@ def _get_gitignore(self, path):
         gitignore = os.path.join(ignore_file_dir, self.GITIGNORE)
 
         if not path_isin(os.path.realpath(gitignore), self.root_dir):
-            raise FileNotInRepoError(path)
+            raise FileNotInRepoError(
+                f"'{path}' is outside of git repository '{self.root_dir}'"
+            )
 
         return entry, gitignore
 


Test patch:
diff --git a/tests/func/test_add.py b/tests/func/test_add.py
--- a/tests/func/test_add.py
+++ b/tests/func/test_add.py
@@ -347,6 +347,24 @@ def test_add_external_file(tmp_dir, dvc, workspace, hash_name, hash_value):
     assert dvc.status() == {}
 
 
+def test_add_external_relpath(tmp_dir, dvc, local_cloud):
+    (fpath,) = local_cloud.gen("file", "file")
+    rel = os.path.relpath(fpath)
+
+    with pytest.raises(StageExternalOutputsError):
+        dvc.add(rel)
+
+    dvc.add(rel, external=True)
+    assert (tmp_dir / "file.dvc").read_text() == (
+        "outs:\n"
+        "- md5: 8c7dd922ad47494fc02c388e12c00eac\n"
+        "  size: 4\n"
+        f"  path: {rel}\n"
+    )
+    assert fpath.read_text() == "file"
+    assert dvc.status() == {}
+
+
 @pytest.mark.parametrize(
     "workspace, hash_name, hash_value",
     [