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.

Discussion:
shcheklein: @Suor @pared guys, please include some title to the PRs. It's easier to understand the context in the emails.
Suor: @efiop may you look at this?
efiop: @Suor sure, sorry for the delay. Also, mind changing your PR and commit names as @shcheklein requested?

Patch:
diff --git a/dvc/stage.py b/dvc/stage.py
index 373de499e..f91aff25d 100644
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -9,6 +9,7 @@ import subprocess
 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 @@ class Stage(object):
         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 @@ class Stage(object):
                     " 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 @@ class Stage(object):
         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
index 2c1aa2927..a9c52185c 100644
--- a/tests/func/test_add.py
+++ b/tests/func/test_add.py
@@ -169,15 +169,17 @@ class TestAddModifiedDir(TestDvc):
         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):