From 690baba0247112fde7b81c2e743ddbb7071aac55 Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel@debian.org>
Date: Tue, 11 Feb 2025 18:51:23 +0100
Subject: [PATCH] Merging upstream version 0.16.6.1.

Signed-off-by: Daniel Baumann <daniel@debian.org>
---
 README.md           | 12 ++++++-
 gita/__main__.py    | 24 +++++++++----
 gita/info.py        | 82 +++++++++++++++++++++++++++++----------------
 setup.py            |  2 +-
 tests/test_main.py  | 19 ++++++-----
 tests/test_utils.py |  6 ++--
 6 files changed, 98 insertions(+), 47 deletions(-)

diff --git a/README.md b/README.md
index 8096080..79c6009 100644
--- a/README.md
+++ b/README.md
@@ -333,8 +333,18 @@ For example, the default setting corresponds to
 branch,commit_msg,commit_time
 ```
 
-Here `branch` includes both branch name and status. To get the branch name alone, use `branch_name`.
+Here `branch` includes both branch name and status.
+The status symbols are similar to the ones used in [spaceship-prompt](https://spaceship-prompt.sh/sections/git/#Git-status-git_status).
 
+To customize these symbols, add a file in `$XDG_CONFIG_HOME/gita/symbols.csv`.
+The default settings corresponds to
+
+```csv
+dirty,staged,untracked,local_ahead,remote_ahead,diverged,in_sync,no_remote
+*,+,?,↑,↓,⇕,,∅
+```
+Only the symbols to be overridden need to be defined.
+You can search unicode symbols [here](https://www.compart.com/en/unicode/).
 
 ### customize git command flags
 
diff --git a/gita/__main__.py b/gita/__main__.py
index 3d091c6..b2bc32b 100644
--- a/gita/__main__.py
+++ b/gita/__main__.py
@@ -165,6 +165,11 @@ def f_clone(args: argparse.Namespace):
 
     if not args.from_file:
         subprocess.run(["git", "clone", args.clonee], cwd=path)
+        # add the cloned repo to gita; group is also supported
+        cloned_path = os.path.join(path, args.clonee.split("/")[-1].split(".")[0])
+        args.paths = [cloned_path]
+        args.recursive = args.auto_group = args.bare = args.skip_submodule = False
+        f_add(args)
         return
 
     if args.preserve_path:
@@ -496,12 +501,6 @@ def main(argv=None):
         "--directory",
         help="Change to DIRECTORY before doing anything.",
     )
-    p_clone.add_argument(
-        "-f",
-        "--from-file",
-        action="store_true",
-        help="If set, clone repos in a config file rendered from `gita freeze`",
-    )
     p_clone.add_argument(
         "-p",
         "--preserve-path",
@@ -515,6 +514,19 @@ def main(argv=None):
         action="store_true",
         help="If set, show command without execution",
     )
+    xgroup = p_clone.add_mutually_exclusive_group()
+    xgroup.add_argument(
+        "-g",
+        "--group",
+        choices=utils.get_groups(),
+        help="If set, add repo to the specified group after cloning, otherwise add to gita without group.",
+    )
+    xgroup.add_argument(
+        "-f",
+        "--from-file",
+        action="store_true",
+        help="If set, clone repos in a config file rendered from `gita freeze`",
+    )
     p_clone.set_defaults(func=f_clone)
 
     p_rename = subparsers.add_parser(
diff --git a/gita/info.py b/gita/info.py
index bfb463b..10d8bea 100644
--- a/gita/info.py
+++ b/gita/info.py
@@ -1,8 +1,8 @@
-import os
 import csv
 import subprocess
 from enum import Enum
 from pathlib import Path
+from collections import namedtuple
 from functools import lru_cache, partial
 from typing import Tuple, List, Callable, Dict
 
@@ -41,11 +41,11 @@ class Color(Enum):
 
 
 default_colors = {
-    "no-remote": Color.white.name,
-    "in-sync": Color.green.name,
+    "no_remote": Color.white.name,
+    "in_sync": Color.green.name,
     "diverged": Color.red.name,
-    "local-ahead": Color.purple.name,
-    "remote-ahead": Color.yellow.name,
+    "local_ahead": Color.purple.name,
+    "remote_ahead": Color.yellow.name,
 }
 
 
@@ -195,49 +195,75 @@ def get_commit_time(prop: Dict[str, str]) -> str:
     return f"({result.stdout.strip()})"
 
 
+default_symbols = {
+    "dirty": "*",
+    "staged": "+",
+    "untracked": "?",
+    "local_ahead": "↑",
+    "remote_ahead": "↓",
+    "diverged": "⇕",
+    "in_sync": "",
+    "no_remote": "∅",
+    "": "",
+}
+
+
+@lru_cache()
+def get_symbols() -> Dict[str, str]:
+    """
+    return status symbols with customization
+    """
+    custom = {}
+    csv_config = Path(common.get_config_fname("symbols.csv"))
+    if csv_config.is_file():
+        with open(csv_config, "r") as f:
+            reader = csv.DictReader(f)
+            custom = next(reader)
+    default_symbols.update(custom)
+    return default_symbols
+
+
 def get_repo_status(prop: Dict[str, str], no_colors=False) -> str:
-    head = get_head(prop["path"])
-    dirty, staged, untracked, color = _get_repo_status(prop, no_colors)
-    info = f"{head:<10} [{dirty+staged+untracked}]"
-    if color:
-        return f"{color}{info:<17}{Color.end}"
-    return f"{info:<17}"
+    branch = get_head(prop["path"])
+    dirty, staged, untracked, situ = _get_repo_status(prop)
+    symbols = get_symbols()
+    info = f"{branch:<10} [{symbols[dirty]+symbols[staged]+symbols[untracked]+symbols[situ]}]"
+
+    if no_colors:
+        return f"{info:<18}"
+    colors = {situ: Color[name].value for situ, name in get_color_encoding().items()}
+    color = colors[situ]
+    return f"{color}{info:<18}{Color.end}"
 
 
 def get_repo_branch(prop: Dict[str, str]) -> str:
     return get_head(prop["path"])
 
 
-def _get_repo_status(prop: Dict[str, str], no_colors: bool) -> Tuple[str]:
+def _get_repo_status(prop: Dict[str, str]) -> Tuple[str, str, str, str]:
     """
     Return the status of one repo
     """
     path = prop["path"]
     flags = prop["flags"]
-    dirty = "*" if run_quiet_diff(flags, [], path) else ""
-    staged = "+" if run_quiet_diff(flags, ["--cached"], path) else ""
-    untracked = "?" if has_untracked(flags, path) else ""
+    dirty = "dirty" if run_quiet_diff(flags, [], path) else ""
+    staged = "staged" if run_quiet_diff(flags, ["--cached"], path) else ""
+    untracked = "untracked" if has_untracked(flags, path) else ""
 
-    if no_colors:
-        return dirty, staged, untracked, ""
-
-    colors = {situ: Color[name].value for situ, name in get_color_encoding().items()}
     diff_returncode = run_quiet_diff(flags, ["@{u}", "@{0}"], path)
-    has_no_remote = diff_returncode == 128
-    has_no_diff = diff_returncode == 0
-    if has_no_remote:
-        color = colors["no-remote"]
-    elif has_no_diff:
-        color = colors["in-sync"]
+    if diff_returncode == 128:
+        situ = "no_remote"
+    elif diff_returncode == 0:
+        situ = "in_sync"
     else:
         common_commit = get_common_commit(path)
         outdated = run_quiet_diff(flags, ["@{u}", common_commit], path)
         if outdated:
             diverged = run_quiet_diff(flags, ["@{0}", common_commit], path)
-            color = colors["diverged"] if diverged else colors["remote-ahead"]
+            situ = "diverged" if diverged else "remote_ahead"
         else:  # local is ahead of remote
-            color = colors["local-ahead"]
-    return dirty, staged, untracked, color
+            situ = "local_ahead"
+    return dirty, staged, untracked, situ
 
 
 ALL_INFO_ITEMS = {
diff --git a/setup.py b/setup.py
index d62cb5f..4950b49 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ with open("README.md", encoding="utf-8") as f:
 setup(
     name="gita",
     packages=["gita"],
-    version="0.16.5",
+    version="0.16.6",
     license="MIT",
     description="Manage multiple git repos with sanity",
     long_description=long_description,
diff --git a/tests/test_main.py b/tests/test_main.py
index 6d05867..a877160 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -130,18 +130,21 @@ class TestLsLl:
         [
             (
                 PATH_FNAME,
-                "repo1 cmaster     [dsu] \x1b[0m msg \nrepo2 cmaster     [dsu] \x1b[0m msg \nxxx   cmaster     [dsu] \x1b[0m msg \n",
+                "repo1 \x1b[31mmaster     [*+?⇕] \x1b[0m msg \nrepo2 \x1b[31mmaster     [*+?⇕] \x1b[0m msg \nxxx   \x1b[31mmaster     [*+?⇕] \x1b[0m msg \n",
             ),
             (PATH_FNAME_EMPTY, ""),
             (
                 PATH_FNAME_CLASH,
-                "repo1 cmaster     [dsu] \x1b[0m msg \nrepo2 cmaster     [dsu] \x1b[0m msg \n",
+                "repo1 \x1b[31mmaster     [*+?⇕] \x1b[0m msg \nrepo2 \x1b[31mmaster     [*+?⇕] \x1b[0m msg \n",
             ),
         ],
     )
     @patch("gita.utils.is_git", return_value=True)
     @patch("gita.info.get_head", return_value="master")
-    @patch("gita.info._get_repo_status", return_value=("d", "s", "u", "c"))
+    @patch(
+        "gita.info._get_repo_status",
+        return_value=("dirty", "staged", "untracked", "diverged"),
+    )
     @patch("gita.info.get_commit_msg", return_value="msg")
     @patch("gita.info.get_commit_time", return_value="")
     @patch("gita.common.get_config_fname")
@@ -566,7 +569,7 @@ def test_set_color(mock_get_fname, tmpdir):
     args = argparse.Namespace()
     args.color_cmd = "set"
     args.color = "b_white"
-    args.situation = "no-remote"
+    args.situation = "no_remote"
     with tmpdir.as_cwd():
         csv_config = Path.cwd() / "colors.csv"
         mock_get_fname.return_value = csv_config
@@ -576,11 +579,11 @@ def test_set_color(mock_get_fname, tmpdir):
         items = info.get_color_encoding()
     info.get_color_encoding.cache_clear()  # avoid side effect
     assert items == {
-        "no-remote": "b_white",
-        "in-sync": "green",
+        "no_remote": "b_white",
+        "in_sync": "green",
         "diverged": "red",
-        "local-ahead": "purple",
-        "remote-ahead": "yellow",
+        "local_ahead": "purple",
+        "remote_ahead": "yellow",
     }
 
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 1e4f125..2936f0e 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -115,17 +115,17 @@ def test_auto_group(repos, paths, expected):
         (
             [{"abc": {"path": "/root/repo/", "type": "", "flags": []}}, False],
             True,
-            "abc \x1b[31mrepo       [*+?] \x1b[0m msg xx",
+            "abc \x1b[31mrepo       [*+?⇕] \x1b[0m msg xx",
         ),
         (
             [{"abc": {"path": "/root/repo/", "type": "", "flags": []}}, True],
             True,
-            "abc repo       [*+?]  msg xx",
+            "abc repo       [*+?⇕]  msg xx",
         ),
         (
             [{"repo": {"path": "/root/repo2/", "type": "", "flags": []}}, False],
             False,
-            "repo \x1b[32mrepo       [?]   \x1b[0m msg xx",
+            "repo \x1b[32mrepo       [?]    \x1b[0m msg xx",
         ),
     ],
 )