Repository: iterative/dvc

Issue:
push/pull cache for all branches
`dvc sync [file]` uploads/downloads data snapshot for only the current brunch.

`dvc sync -all [file]` should upload\download all versions of the file(s).


Discussion:
I am not sure it is worth implementing, as there are usually more branches that don't even belong to you(i.e. various branches from origin, upstream and etc), which makes pushing/pulling all branches counter-productive, as in those rare cases when you indeed need to sync a few branches, you can easily do so by checking out those branches with git yourself. Actually, same logic applies to `dvc metrics`, but there the cost of it was pretty low, so it was an easy choice to just implement it. Lets discuss this one later. Moving to 0.9.8 for consideration.

Resolving patch:
diff --git a/dvc/cli.py b/dvc/cli.py
--- a/dvc/cli.py
+++ b/dvc/cli.py
@@ -185,6 +185,11 @@ def parse_args(argv=None):
     pull_parser.add_argument('-r',
                         '--remote',
                         help='Remote repository to pull from')
+    pull_parser.add_argument(
+                        '--all-branches',
+                        action='store_true',
+                        default=False,
+                        help='Fetch cache for all branches.')
     pull_parser.set_defaults(func=CmdDataPull)
 
     # Push
@@ -195,6 +200,11 @@ def parse_args(argv=None):
     push_parser.add_argument('-r',
                         '--remote',
                         help='Remote repository to push to')
+    push_parser.add_argument(
+                        '--all-branches',
+                        action='store_true',
+                        default=False,
+                        help='Push cache for all branches.')
     push_parser.set_defaults(func=CmdDataPush)
 
     # Fetch
@@ -205,6 +215,11 @@ def parse_args(argv=None):
     fetch_parser.add_argument('-r',
                         '--remote',
                         help='Remote repository to fetch from')
+    fetch_parser.add_argument(
+                        '--all-branches',
+                        action='store_true',
+                        default=False,
+                        help='Fetch cache for all branches.')
     fetch_parser.set_defaults(func=CmdDataFetch)
 
     # Status
@@ -279,6 +294,11 @@ def parse_args(argv=None):
                         'gc',
                         parents=[parent_parser],
                         help='Collect garbage')
+    gc_parser.add_argument(
+                        '--all-branches',
+                        action='store_true',
+                        default=False,
+                        help='Collect garbage for all branches.')
     gc_parser.set_defaults(func=CmdGC)
 
     # Config
diff --git a/dvc/project.py b/dvc/project.py
--- a/dvc/project.py
+++ b/dvc/project.py
@@ -236,24 +236,26 @@ def _used_cache(self, target=None):
 
         return list(cache_set)
 
-    def gc(self):
-        clist = self._used_cache()
-        for cache in self.cache.local.all():
-            if cache in clist:
-                continue
-            os.unlink(cache)
-            self.logger.info(u'\'{}\' was removed'.format(self.to_dvc_path(cache)))
+    def gc(self, all_branches=False):
+        for branch in self.scm.brancher(all_branches=all_branches):
+            clist = self._used_cache()
+            for cache in self.cache.local.all():
+                if cache in clist:
+                    continue
+                os.unlink(cache)
+                self.logger.info(u'\'{}\' was removed'.format(self.to_dvc_path(cache)))
 
-    def push(self, target=None, jobs=1, remote=None):
-        return self.cloud.push(self._used_cache(target), jobs, remote=remote)
+    def push(self, target=None, jobs=1, remote=None, all_branches=False):
+        for branch in self.scm.brancher(all_branches=all_branches):
+            self.cloud.push(self._used_cache(target), jobs, remote=remote)
 
-    def fetch(self, target=None, jobs=1, remote=None):
-        return self.cloud.pull(self._used_cache(target), jobs, remote=remote)
+    def fetch(self, target=None, jobs=1, remote=None, all_branches=False):
+        for branch in self.scm.brancher(all_branches=all_branches):
+            self.cloud.pull(self._used_cache(target), jobs, remote=remote)
 
-    def pull(self, target=None, jobs=1, remote=None):
-        ret = self.fetch(target, jobs, remote=remote)
+    def pull(self, target=None, jobs=1, remote=None, all_branches=False):
+        self.fetch(target, jobs, remote=remote, all_branches=all_branches)
         self.checkout()
-        return ret
 
     def _local_status(self, target=None):
         status = {}
@@ -342,12 +344,7 @@ def _read_metric(self, path, json_path=None, tsv_path=None, htsv_path=None):
 
     def metrics_show(self, path=None, json_path=None, tsv_path=None, htsv_path=None, all_branches=False):
         res = {}
-        saved = self.scm.active_branch()
-        branches = self.scm.list_branches() if all_branches else [saved]
-        for branch in branches:
-            self.scm.checkout(branch)
-            self.checkout()
-
+        for branch in self.scm.brancher(all_branches=all_branches):
             metrics = filter(lambda o: o.metric, self.outs())
             fnames = [path] if path else map(lambda o: o.path, metrics)
             for fname in fnames:
@@ -364,9 +361,6 @@ def metrics_show(self, path=None, json_path=None, tsv_path=None, htsv_path=None,
 
                 res[branch][rel] = metric
 
-        self.scm.checkout(saved)
-        self.checkout()
-
         for branch, val in res.items():
             self.logger.info('{}:'.format(branch))
             for fname, metric in val.items():
diff --git a/dvc/scm.py b/dvc/scm.py
--- a/dvc/scm.py
+++ b/dvc/scm.py
@@ -43,6 +43,15 @@ def checkout(self, branch):
     def branch(self, branch):
         pass
 
+    def brancher(self, branches=None, all_branches=False):
+        saved = self.active_branch()
+        if not branches:
+            branches = self.list_branches() if all_branches else [saved]
+        for branch in branches:
+            self.checkout(branch)
+            yield branch
+        self.checkout(saved)
+
     def untracked_files(self):
         pass
 


Test patch: