from typing import List, Tuple  # noqa
from path import Path
import cli_ui as ui

import tsrc
import tsrc.executor
import tsrc.git


class BadBranches(tsrc.Error):
    pass


class Syncer(tsrc.executor.Task[tsrc.Repo]):
    def __init__(self, workspace_path: Path, *, force: bool = False) -> None:
        self.workspace_path = workspace_path
        self.bad_branches = list()  # type: List[Tuple[str, str, str]]
        self.force = force

    def on_start(self, *, num_items: int) -> None:
        ui.info_1("Synchronizing workspace")

    def on_failure(self, *, num_errors: int) -> None:
        ui.error("Failed to synchronize workspace")

    def display_item(self, repo: tsrc.Repo) -> str:
        return repo.src

    def process(self, index: int, count: int, repo: tsrc.Repo) -> None:
        ui.info_count(index, count, repo.src)
        repo_path = self.workspace_path / repo.src
        self.fetch(repo)
        ref = None

        if repo.tag:
            ref = repo.tag
        elif repo.sha1:
            ref = repo.sha1

        if ref:
            self.sync_repo_to_ref(repo_path, ref)
        else:
            self.check_branch(repo, repo_path)
            self.sync_repo_to_branch(repo_path)

    def check_branch(self, repo: tsrc.Repo, repo_path: Path) -> None:
        current_branch = None
        try:
            current_branch = tsrc.git.get_current_branch(repo_path)
        except tsrc.Error:
            raise tsrc.Error("Not on any branch")

        if current_branch and current_branch != repo.branch:
            self.bad_branches.append((repo.src, current_branch, repo.branch))

    def fetch(self, repo: tsrc.Repo) -> None:
        repo_path = self.workspace_path / repo.src
        for remote in repo.remotes:
            try:
                ui.info_2("Fetching", remote.name)
                cmd = ["fetch", "--tags", "--prune", remote.name]
                if self.force:
                    cmd.append("--force")
                tsrc.git.run(repo_path, *cmd)
            except tsrc.Error:
                raise tsrc.Error("fetch from %s failed" % remote.name)

    @staticmethod
    def sync_repo_to_ref(repo_path: Path, ref: str) -> None:
        ui.info_2("Resetting to", ref)
        status = tsrc.git.get_status(repo_path)
        if status.dirty:
            raise tsrc.Error("%s is dirty, skipping" % repo_path)
        try:
            tsrc.git.run(repo_path, "reset", "--hard", ref)
        except tsrc.Error:
            raise tsrc.Error("updating ref failed")

    @staticmethod
    def sync_repo_to_branch(repo_path: Path) -> None:
        ui.info_2("Updating branch")
        try:
            tsrc.git.run(repo_path, "merge", "--ff-only", "@{upstream}")
        except tsrc.Error:
            raise tsrc.Error("updating branch failed")

    def display_bad_branches(self) -> None:
        if not self.bad_branches:
            return
        ui.error("Some projects were not on the correct branch")
        headers = ("project", "actual", "expected")
        data = [
            ((ui.bold, name), (ui.red, actual), (ui.green, expected))
            for (name, actual, expected) in self.bad_branches
        ]
        ui.info_table(data, headers=headers)
        raise BadBranches()

@@ -32,16 +32,28 @@
  # noqa%0A
+import attr%0A
 from pat
@@ -180,16 +180,186 @@
  pass%0A%0A%0A
+@attr.s(frozen=True)%0Aclass RepoAtIncorrectBranchDescription:%0A    src = attr.ib()  # type: str%0A    actual = attr.ib()  # type: str%0A    expected = attr.ib()  # type: str%0A%0A%0A
 class Sy
@@ -571,28 +571,40 @@
 ist%5B
-Tuple%5Bstr, str, str%5D
+RepoAtIncorrectBranchDescription
 %5D%0A  
@@ -1758,17 +1758,91 @@
 .append(
-(
+%0A                RepoAtIncorrectBranchDescription(%0A                    src=
 repo.src
@@ -1843,16 +1843,23 @@
 po.src, 
+actual=
 current_
@@ -1866,16 +1866,25 @@
 branch, 
+expected=
 repo.bra
@@ -1886,17 +1886,47 @@
 o.branch
-)
+%0A                )%0A            
 )%0A%0A    d
@@ -3351,20 +3351,21 @@
 i.bold, 
-name
+x.src
 ), (ui.r
@@ -3368,16 +3368,18 @@
 ui.red, 
+x.
 actual),
@@ -3390,16 +3390,18 @@
 .green, 
+x.
 expected
@@ -3423,32 +3423,9 @@
 for 
-(name, actual, expected)
+x
  in 

Use a nice struct to collect repositories descriptions