tgit 0.2.2__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,41 @@
1
- Metadata-Version: 2.1
2
- Name: tgit
3
- Version: 0.2.2
4
- Summary: Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze.
5
- Author-email: Jannchie <jannchie@gmail.com>
6
- Maintainer-email: Jannchie <jannchie@gmail.com>
7
- Keywords: git,tool,changelog,version,commit
8
- Classifier: Development Status :: 3 - Alpha
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Topic :: Software Development :: Version Control
13
- Classifier: Topic :: Utilities
14
- Classifier: Typing :: Typed
15
- Requires-Python: >=3.6
16
- Description-Content-Type: text/markdown
17
- Requires-Dist: rich
18
- Requires-Dist: PyYAML
19
- Requires-Dist: inquirer
1
+ Metadata-Version: 2.1
2
+ Name: tgit
3
+ Version: 0.4.0
4
+ Summary: Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze.
5
+ License: MIT
6
+ Keywords: git,tool,changelog,version,commit
7
+ Author: Jannchie
8
+ Author-email: jannchie@gmail.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Version Control
19
+ Classifier: Topic :: Utilities
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
22
+ Requires-Dist: inquirer (>=3.2.5,<4.0.0)
23
+ Requires-Dist: rich (>=13.7.1,<14.0.0)
24
+ Description-Content-Type: text/markdown
25
+
26
+ # tgit
27
+
28
+ Tool for managing git repositories.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install tgit
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```bash
39
+ tgit --help
40
+ ```
41
+
tgit-0.4.0/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # tgit
2
+
3
+ Tool for managing git repositories.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install tgit
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ tgit --help
15
+ ```
@@ -1,20 +1,11 @@
1
-
2
-
3
- [build-system]
4
- requires = ["setuptools", "wheel"]
5
- build-backend = "setuptools.build_meta"
6
-
7
- [project]
1
+ [tool.poetry]
8
2
  name = "tgit"
9
- version = "0.2.2"
3
+ version = "0.4.0"
10
4
  description = "Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze."
11
- requires-python = ">=3.6"
12
- keywords = ["git", "tool", "changelog", "version", "commit"]
13
- dependencies = ["rich", "PyYAML", "inquirer"]
14
- license = { file = "LICENSE" }
15
- authors = [{ name = "Jannchie", email = "jannchie@gmail.com" }]
16
- maintainers = [{ name = "Jannchie", email = "jannchie@gmail.com" }]
5
+ authors = ["Jannchie <jannchie@gmail.com>"]
17
6
  readme = "README.md"
7
+ license = "MIT"
8
+ keywords = ["git", "tool", "changelog", "version", "commit"]
18
9
  classifiers = [
19
10
  "Development Status :: 3 - Alpha",
20
11
  "Intended Audience :: Developers",
@@ -25,5 +16,19 @@ classifiers = [
25
16
  "Typing :: Typed",
26
17
  ]
27
18
 
28
- [project.scripts]
19
+
20
+ [tool.poetry.dependencies]
21
+ python = "^3.9"
22
+ rich = "^13.7.1"
23
+ PyYAML = "^6.0.1"
24
+ inquirer = "^3.2.5"
25
+
26
+
27
+ [tool.poetry.scripts]
29
28
  tgit = "tgit.cli:main"
29
+ gitt = "tgit.cli:main"
30
+
31
+
32
+ [build-system]
33
+ requires = ["poetry-core"]
34
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,247 @@
1
+ import re
2
+ import warnings
3
+ from collections import defaultdict
4
+ from dataclasses import dataclass
5
+
6
+ import git
7
+
8
+
9
+ def get_latest_git_tag(repo: git.Repo):
10
+ return get_tag_by_idx(repo, -1)
11
+
12
+
13
+ def get_tag_by_idx(repo: git.Repo, idx: int):
14
+ try:
15
+ if tags := sorted(repo.tags, key=lambda t: t.commit.committed_datetime):
16
+ return tags[idx].name
17
+ else:
18
+ return None
19
+ except Exception as e:
20
+ print(f"Error: {e}")
21
+ return None
22
+
23
+
24
+ def get_first_commit_hash(repo: git.Repo) -> str:
25
+ return next(
26
+ (commit.hexsha for commit in repo.iter_commits() if not commit.parents),
27
+ None,
28
+ )
29
+
30
+
31
+ def get_commit_hash_from_tag(repo: git.Repo, tag):
32
+ try:
33
+ return repo.tags[tag].commit.hexsha
34
+ except Exception as e:
35
+ print(f"Error: {e}")
36
+ return None
37
+
38
+
39
+ def define_changelog_parser(subparsers):
40
+ parser_changelog = subparsers.add_parser("changelog", help="generate changelogs")
41
+ parser_changelog.add_argument("-f", "--from", help="From hash/tag", type=str, dest="from_raw")
42
+ parser_changelog.add_argument("-t", "--to", help="To hash/tag", type=str, dest="to_raw")
43
+ parser_changelog.add_argument("-v", "--verbose", action="count", default=0, help="increase output verbosity", dest="verbose")
44
+ parser_changelog.add_argument("path", help="repository path", type=str, nargs="?", default=".")
45
+ parser_changelog.set_defaults(func=handle_changelog)
46
+
47
+
48
+ @dataclass
49
+ class ChangelogArgs:
50
+ from_raw: str
51
+ to_raw: str
52
+ verbose: int
53
+ path: str
54
+
55
+
56
+ def get_simple_hash(repo: git.Repo, hash, length=7):
57
+ try:
58
+ return repo.git.rev_parse(hash, short=length)
59
+ except Exception as e:
60
+ print(f"Error: {e}")
61
+ return None
62
+
63
+
64
+ def ref_to_hash(repo: git.Repo, ref: str, length=7):
65
+ try:
66
+ return repo.git.rev_parse(ref, short=length)
67
+ except Exception as e:
68
+ print(f"Error: {e}")
69
+ return None
70
+
71
+
72
+ commit_pattern = re.compile(
73
+ r"(?P<emoji>:.+:|(\uD83C[\uDF00-\uDFFF])|(\uD83D[\uDC00-\uDE4F\uDE80-\uDEFF])|[\u2600-\u2B55])?( *)?(?P<type>[a-z]+)(\((?P<scope>.+?)\))?(?P<breaking>!)?: (?P<description>.+)",
74
+ re.IGNORECASE,
75
+ )
76
+
77
+
78
+ def resolve_from_ref(repo, from_raw):
79
+ if from_raw is not None:
80
+ return from_raw
81
+ last_tag = get_latest_git_tag(repo)
82
+ return get_first_commit_hash(repo) if last_tag is None else last_tag
83
+
84
+
85
+ @dataclass
86
+ class Author:
87
+ name: str
88
+ email: str
89
+
90
+ def __str__(self):
91
+ return f"{self.name} <{self.email}>"
92
+
93
+
94
+ class TGITCommit:
95
+ def __init__(self, repo: git.Repo, commit: git.Commit, message_dict: dict):
96
+ commit_date = commit.committed_datetime
97
+
98
+ co_author_raws = [line for line in commit.message.split("\n") if line.lower().startswith("co-authored-by:")]
99
+ co_author_pattern = re.compile(r"Co-authored-by: (?P<name>.+?) <(?P<email>.+?)>", re.IGNORECASE)
100
+ co_authors = [co_author_pattern.match(co_author).groupdict() for co_author in co_author_raws]
101
+ authors = [{"name": commit.author.name, "email": commit.author.email}] + co_authors
102
+ self.authors: list[Author] = [Author(**kwargs) for kwargs in authors]
103
+ self.date = commit_date
104
+ self.emoji = message_dict.get("emoji")
105
+ self.type = message_dict.get("type")
106
+ self.scope = message_dict.get("scope")
107
+ self.description = message_dict.get("description")
108
+ self.breaking = bool(message_dict.get("breaking"))
109
+ self.hash = repo.git.rev_parse(commit.hexsha, short=7)
110
+
111
+ def __str__(self) -> str:
112
+ authors_str = ", ".join(str(author) for author in self.authors)
113
+ date_str = self.date.strftime("%Y-%m-%d %H:%M:%S")
114
+ return (
115
+ f"Hash: {self.hash}\n"
116
+ f"Breaking: {self.breaking}\n"
117
+ f"Commit: {self.emoji or ''} {self.type or ''}{f'({self.scope})' if self.scope else ''}: {self.description}\n"
118
+ f"Date: {date_str}\n"
119
+ f"Authors: {authors_str}\n"
120
+ )
121
+
122
+
123
+ def format_names(names):
124
+ if not names:
125
+ return ""
126
+
127
+ if len(names) == 1:
128
+ return f"By {names[0]}"
129
+
130
+ if len(names) == 2:
131
+ return f"By {names[0]} and {names[1]}"
132
+
133
+ formatted_names = ", ".join(names[:-1])
134
+ formatted_names += f" and {names[-1]}"
135
+
136
+ return f"By {formatted_names}"
137
+
138
+
139
+ def get_remote_uri(url: str):
140
+ # SSH URL regex, with groups for domain, namespace and repo name
141
+ ssh_pattern = re.compile(r"git@([\w\.]+):(.+)/(.+)\.git")
142
+ # HTTPS URL regex, with groups for domain, namespace and repo name
143
+ https_pattern = re.compile(r"https://([\w\.]+)/(.+)/(.+)\.git")
144
+
145
+ if ssh_match := ssh_pattern.match(url):
146
+ domain, namespace, repo_name = ssh_match[1], ssh_match[2], ssh_match[3]
147
+ return f"{domain}/{namespace}/{repo_name}" # "domain/namespace/repo_name"
148
+
149
+ if https_match := https_pattern.match(url):
150
+ domain, namespace, repo_name = https_match[1], https_match[2], https_match[3]
151
+ return f"{domain}/{namespace}/{repo_name}" # "domain/namespace/repo_name"
152
+
153
+ return None
154
+
155
+
156
+ def get_commits(repo: git.Repo, from_hash: str, to_hash: str) -> list[TGITCommit]:
157
+ raw_commits = list(repo.iter_commits(f"{from_hash}...{to_hash}"))
158
+ tgit_commits = []
159
+ for commit in raw_commits:
160
+ if m := commit_pattern.match(commit.message):
161
+ message_dict = m.groupdict()
162
+ tgit_commits.append(TGITCommit(repo, commit, message_dict))
163
+ return tgit_commits
164
+
165
+
166
+ def group_commits_by_type(commits: list[TGITCommit]) -> dict[str, list[TGITCommit]]:
167
+ commits_by_type = defaultdict(list)
168
+ for commit in commits:
169
+ if commit.breaking:
170
+ commits_by_type["breaking"].append(commit)
171
+ else:
172
+ commits_by_type[commit.type].append(commit)
173
+ return commits_by_type
174
+
175
+
176
+ def generate_changelog(commits_by_type: dict[str, list[TGITCommit]], from_ref: str, to_ref: str, remote_uri: str = None) -> str:
177
+ order = ["breaking", "feat", "fix", "refactor", "perf", "style", "docs", "chore"]
178
+ names = [
179
+ ":rocket: Breaking Changes",
180
+ ":sparkles: Features",
181
+ ":adhesive_bandage: Fixes",
182
+ ":art: Refactors",
183
+ ":zap: Performance Improvements",
184
+ ":lipstick: Styles",
185
+ ":memo: Documentation",
186
+ ":wrench: Chores",
187
+ ]
188
+ out_str = ""
189
+ out_str = f"## {to_ref}\n\n"
190
+ if remote_uri:
191
+ out_str += f"[{from_ref}...{to_ref}](https://{remote_uri}/compare/{from_ref}...{to_ref})\n\n"
192
+ else:
193
+ out_str += f"{from_ref}...{to_ref}\n\n"
194
+
195
+ def get_hash_link(commit: TGITCommit):
196
+ if remote_uri:
197
+ return f"[{commit.hash}](https://{remote_uri}/commit/{commit.hash})"
198
+ return commit.hash
199
+
200
+ for i, o in enumerate(order):
201
+ if commits := commits_by_type.get(o):
202
+ title = f"### {names[i]}\n\n"
203
+ out_str += title
204
+ # Sort commits by scope, if scope is None, put it to last
205
+ commits.sort(key=lambda c: "zzzzz" if c.scope is None else c.scope)
206
+ for commit in commits:
207
+
208
+ authors_str = format_names([f"[{a.name}](mailto:{a.email})" for a in commit.authors])
209
+ if commit.scope:
210
+ line = f"- **{commit.scope}**: {commit.description} - {authors_str} in {get_hash_link(commit)}\n"
211
+ else:
212
+ line = f"- {commit.description} - {authors_str} in {get_hash_link(commit)}\n"
213
+ out_str += line
214
+ out_str += "\n"
215
+ return out_str
216
+
217
+
218
+ def handle_changelog(args: ChangelogArgs):
219
+ repo = git.Repo(args.path)
220
+ from_ref = resolve_from_ref(repo, args.from_raw)
221
+ to_ref = "HEAD" if args.to_raw is None else args.to_raw
222
+ if to_ref == "HEAD":
223
+ latest_commit = repo.head.commit
224
+ tags = repo.tags
225
+ latest_commit_tags = [tag for tag in tags if tag.commit == latest_commit]
226
+ if latest_commit_tags:
227
+ to_ref = from_ref
228
+ from_ref = get_tag_by_idx(repo, -2)
229
+ if from_ref is None:
230
+ from_ref = get_first_commit_hash(repo)
231
+ else:
232
+ warnings.warn("HEAD is not a tag, changelog will be generated from the last tag to HEAD.")
233
+ from_hash = ref_to_hash(repo, from_ref)
234
+ to_hash = ref_to_hash(repo, to_ref)
235
+
236
+ try:
237
+ origin_url = repo.remote().url
238
+ remote_uri = get_remote_uri(origin_url)
239
+ except ValueError:
240
+ warnings.warn("Origin not found, some of the link generation functions could not be enabled.")
241
+ remote_uri = None
242
+
243
+ tgit_commits = get_commits(repo, from_hash, to_hash)
244
+ commits_by_type = group_commits_by_type(tgit_commits)
245
+ changelog = generate_changelog(commits_by_type, from_ref, to_ref, remote_uri)
246
+ print()
247
+ print(changelog)
@@ -5,22 +5,21 @@ from typing import Optional
5
5
  from tgit.settings import settings
6
6
  from tgit.utils import get_commit_command, run_command, type_emojis
7
7
 
8
+ commit_type = ["feat", "fix", "chore", "docs", "style", "refactor", "perf"]
9
+
8
10
 
9
11
  def define_commit_parser(subparsers):
10
12
  commit_type = ["feat", "fix", "chore", "docs", "style", "refactor", "perf"]
11
- prefix = ["!", ""]
12
13
  commit_settings = settings.get("commit", {})
13
14
  types_settings = commit_settings.get("types", [])
14
15
  for data in types_settings:
15
16
  type_emojis[data.get("type")] = data.get("emoji")
16
17
  commit_type.append(data.get("type"))
17
18
 
18
- choices = ["".join(data) for data in itertools.product(commit_type, prefix)] + ["ci", "test", "version"]
19
19
  parser_commit = subparsers.add_parser("commit", help="commit changes following the conventional commit format")
20
20
  parser_commit.add_argument(
21
21
  "type",
22
22
  help="commit type",
23
- choices=choices,
24
23
  )
25
24
  parser_commit.add_argument("scope", help="commit scope", type=str, nargs="?")
26
25
  parser_commit.add_argument("message", help="commit message", type=str)
@@ -41,6 +40,14 @@ class CommitArgs:
41
40
 
42
41
  def handle_commit(args: CommitArgs):
43
42
 
43
+ global commit_type
44
+ prefix = ["", "!"]
45
+ choices = ["".join(data) for data in itertools.product(commit_type, prefix)] + ["ci", "test", "version"]
46
+ if args.type not in choices:
47
+ print(f"Invalid type: {args.type}")
48
+ print(f"Valid types: {choices}")
49
+ return
50
+
44
51
  commit_type = args.type
45
52
  commit_scope = args.scope
46
53
  commit_msg = args.message
@@ -2,7 +2,6 @@ import subprocess
2
2
  import sys
3
3
  from typing import Optional
4
4
 
5
- import inquirer
6
5
  import rich
7
6
  from rich.panel import Panel
8
7
  from rich.syntax import Syntax
@@ -11,6 +10,7 @@ from tgit.settings import settings
11
10
 
12
11
  console = rich.get_console()
13
12
 
13
+ import inquirer
14
14
 
15
15
  type_emojis = {
16
16
  "feat": ":sparkles:",
@@ -57,6 +57,7 @@ def run_command(command: str):
57
57
  console.print(panel)
58
58
 
59
59
  if not settings.get("skip_confirm", False):
60
+
60
61
  ok = inquirer.prompt([inquirer.Confirm("continue", message="Do you want to continue?", default=True)])
61
62
  if not ok or not ok["continue"]:
62
63
  return
@@ -105,6 +105,7 @@ def get_prev_version():
105
105
  with open("VERSION") as f:
106
106
  version = f.read().strip()
107
107
  return Version.from_str(version)
108
+
108
109
  elif os.path.exists("VERSION.txt"):
109
110
  with open("VERSION.txt") as f:
110
111
  version = f.read().strip()
@@ -123,6 +124,7 @@ def get_prev_version():
123
124
 
124
125
 
125
126
  def handle_version(args: VersionArgs):
127
+
126
128
  verbose = args.verbose
127
129
 
128
130
  # check if there is uncommitted changes
@@ -186,6 +188,8 @@ def handle_version(args: VersionArgs):
186
188
  )
187
189
  ]
188
190
  )
191
+ if not ans:
192
+ return
189
193
  release = ans["identifier"]
190
194
  next_version.release = release
191
195
  if target.bump == "custom":
@@ -275,6 +279,36 @@ def handle_version(args: VersionArgs):
275
279
  with open("pyproject.toml", "w") as f:
276
280
  f.write(new_pyproject_toml)
277
281
 
282
+ if os.path.exists("setup.py"):
283
+ if verbose > 0:
284
+ console.print("Updating setup.py")
285
+ with open("setup.py", "r") as f:
286
+ setup_py = f.read()
287
+ new_setup_py = re.sub(r"version=['\"].*?['\"]", f"version='{next_version_str}'", setup_py)
288
+ with open("setup.py", "w") as f:
289
+ f.write(new_setup_py)
290
+
291
+ if os.path.exists("Cargo.toml"):
292
+ if verbose > 0:
293
+ console.print("Updating Cargo.toml")
294
+ with open("Cargo.toml", "r") as f:
295
+ cargo_toml = f.read()
296
+ new_cargo_toml = re.sub(r"version\s*=\s*\".*?\"", f'version = "{next_version_str}"', cargo_toml)
297
+ with open("Cargo.toml", "w") as f:
298
+ f.write(new_cargo_toml)
299
+
300
+ if os.path.exists("VERSION"):
301
+ if verbose > 0:
302
+ console.print("Updating VERSION")
303
+ with open("VERSION", "w") as f:
304
+ f.write(next_version_str)
305
+
306
+ if os.path.exists("VERSION.txt"):
307
+ if verbose > 0:
308
+ console.print("Updating VERSION.txt")
309
+ with open("VERSION.txt", "w") as f:
310
+ f.write(next_version_str)
311
+
278
312
  git_tag = f"v{next_version_str}"
279
313
 
280
314
  commands = []
@@ -378,8 +412,3 @@ def define_version_parser(subparsers):
378
412
  version_group.add_argument("-pM", "--premajor", help="premajor version", type=str)
379
413
  version_group.add_argument("version", help="version to bump to", type=str, nargs="?")
380
414
  parser_version.set_defaults(func=handle_version)
381
- parser_version.set_defaults(func=handle_version)
382
- parser_version.set_defaults(func=handle_version)
383
- parser_version.set_defaults(func=handle_version)
384
- parser_version.set_defaults(func=handle_version)
385
- parser_version.set_defaults(func=handle_version)
tgit-0.2.2/README.md DELETED
File without changes
tgit-0.2.2/setup.cfg DELETED
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,25 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
- from tgit.utils import console
4
-
5
-
6
- def define_changelog_parser(subparsers):
7
- parser_changelog = subparsers.add_parser("changelog", help="generate changelogs")
8
- parser_changelog.add_argument("-f", "--from", help="From hash/tag", type=str, dest="from_raw")
9
- parser_changelog.add_argument("-t", "--to", help="To hash/tag", type=str, dest="to_raw")
10
- parser_changelog.add_argument("-v", "--verbose", action="count", default=0, help="increase output verbosity")
11
- parser_changelog.set_defaults(func=handle_changelog)
12
-
13
-
14
- @dataclass
15
- class ChangelogArgs:
16
- from_raw: str
17
- to_raw: str
18
-
19
-
20
- def handle_changelog(args: ChangelogArgs):
21
- from_raw = args.from_raw
22
- to_raw = args.to_raw
23
- console.log(f"{from_raw} -> {to_raw}")
24
- console.log(args)
25
- console.log("WIP")
@@ -1,19 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: tgit
3
- Version: 0.2.2
4
- Summary: Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze.
5
- Author-email: Jannchie <jannchie@gmail.com>
6
- Maintainer-email: Jannchie <jannchie@gmail.com>
7
- Keywords: git,tool,changelog,version,commit
8
- Classifier: Development Status :: 3 - Alpha
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Topic :: Software Development :: Version Control
13
- Classifier: Topic :: Utilities
14
- Classifier: Typing :: Typed
15
- Requires-Python: >=3.6
16
- Description-Content-Type: text/markdown
17
- Requires-Dist: rich
18
- Requires-Dist: PyYAML
19
- Requires-Dist: inquirer
@@ -1,15 +0,0 @@
1
- README.md
2
- pyproject.toml
3
- tgit/__init__.py
4
- tgit/changelog.py
5
- tgit/cli.py
6
- tgit/commit.py
7
- tgit/settings.py
8
- tgit/utils.py
9
- tgit/version.py
10
- tgit.egg-info/PKG-INFO
11
- tgit.egg-info/SOURCES.txt
12
- tgit.egg-info/dependency_links.txt
13
- tgit.egg-info/entry_points.txt
14
- tgit.egg-info/requires.txt
15
- tgit.egg-info/top_level.txt
@@ -1 +0,0 @@
1
-
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- tgit = tgit.cli:main
@@ -1,3 +0,0 @@
1
- rich
2
- PyYAML
3
- inquirer
@@ -1 +0,0 @@
1
- tgit
File without changes
File without changes
File without changes