Repository: iterative/dvc

Issue:
Consider changing behavior of `dvc add` related to wdir
I suggest we change the `dvc add path` behavior to always treat the deepest-level directory in path as a wdir, such that in resulting dvc files wdir will always be `.` and not relative to cwd. Similarly, `.gitignore` should be placed in the same deepest-level directory.

The reason for this proposed change is to 
- make dvc add behavior independant of cwd and more intuitive overall
- make resulting dvc-files and .gitignores as portable as possible
- wdir serve no purpose in `add`ed files anyway

An alternative would be to remove wdir for `add`ed dvc files altogather.


Comments:
@ematvey Regarding the gitignore, it is already placed right beside the output file:
```
mkdir -p a/b
echo foo > a/b/foo
dvc add a/b/foo
```
will create a/b/.gitignore. Maybe I've misunderstood your question?
About the wdir and paths in that case, you are right, it is incorrect. Looks like we haven't noticed this bug, but it used to be(and was intended for those precise reasons of portability) to make wdir . and path `foo`.
I might be mistaken about `.gitignore`.

Patch:
diff --git a/dvc/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -9,6 +9,7 @@
 import logging
 import signal
 import threading
+from itertools import chain
 
 from dvc.utils import relpath
 from dvc.utils.compat import pathlib
@@ -438,6 +439,7 @@ def create(repo, **kwargs):
         wdir = kwargs.get("wdir", None)
         cwd = kwargs.get("cwd", None)
         fname = kwargs.get("fname", None)
+        add = kwargs.get("add", False)
 
         # Backward compatibility for `cwd` option
         if wdir is None and cwd is not None:
@@ -449,8 +451,8 @@ def create(repo, **kwargs):
                     " directory.".format(fname=fname)
                 )
             wdir = cwd
-        else:
-            wdir = os.curdir if wdir is None else wdir
+        elif wdir is None:
+            wdir = os.curdir
 
         stage = Stage(
             repo=repo,
@@ -468,11 +470,18 @@ def create(repo, **kwargs):
         stage._check_duplicated_arguments()
 
         if not fname:
-            fname = Stage._stage_fname(
-                stage.outs, add=kwargs.get("add", False)
-            )
+            fname = Stage._stage_fname(stage.outs, add)
         stage._check_dvc_filename(fname)
 
+        # Autodetecting wdir for add, we need to create outs first to do that,
+        # so we start with wdir = . and remap out paths later.
+        if add and kwargs.get("wdir") is None and cwd is None:
+            wdir = os.path.dirname(fname)
+
+            for out in chain(stage.outs, stage.deps):
+                if out.is_in_repo:
+                    out.def_path = relpath(out.path_info, wdir)
+
         wdir = os.path.abspath(wdir)
 
         if cwd is not None:


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
@@ -169,15 +169,17 @@ def test(self):
         self.assertTrue(stages[0] is not None)
 
 
-class TestAddFileInDir(TestDvc):
-    def test(self):
-        stages = self.dvc.add(self.DATA_SUB)
-        self.assertEqual(len(stages), 1)
-        stage = stages[0]
-        self.assertNotEqual(stage, None)
-        self.assertEqual(len(stage.deps), 0)
-        self.assertEqual(len(stage.outs), 1)
-        self.assertEqual(stage.relpath, self.DATA_SUB + ".dvc")
+def test_add_file_in_dir(repo_dir, dvc_repo):
+    stage, = dvc_repo.add(repo_dir.DATA_SUB)
+
+    assert stage is not None
+    assert len(stage.deps) == 0
+    assert len(stage.outs) == 1
+    assert stage.relpath == repo_dir.DATA_SUB + ".dvc"
+
+    # Current dir should not be taken into account
+    assert stage.wdir == os.path.dirname(stage.path)
+    assert stage.outs[0].def_path == "data_sub"
 
 
 class TestAddExternalLocalFile(TestDvc):