reposnap 0.6.5__py3-none-any.whl → 0.7.0__py3-none-any.whl
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.
- reposnap/controllers/project_controller.py +16 -4
- reposnap/core/git_repo.py +68 -0
- reposnap/interfaces/cli.py +6 -0
- {reposnap-0.6.5.dist-info → reposnap-0.7.0.dist-info}/METADATA +46 -2
- {reposnap-0.6.5.dist-info → reposnap-0.7.0.dist-info}/RECORD +8 -8
- {reposnap-0.6.5.dist-info → reposnap-0.7.0.dist-info}/WHEEL +0 -0
- {reposnap-0.6.5.dist-info → reposnap-0.7.0.dist-info}/entry_points.txt +0 -0
- {reposnap-0.6.5.dist-info → reposnap-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,7 +15,7 @@ class ProjectController:
|
|
15
15
|
self.args = args
|
16
16
|
# Treat positional arguments as literal file/directory names.
|
17
17
|
input_paths = [
|
18
|
-
Path(p) for p in (args.paths if hasattr(args, "paths") else [
|
18
|
+
Path(p) for p in (args.paths if hasattr(args, "paths") else [])
|
19
19
|
]
|
20
20
|
self.input_paths = []
|
21
21
|
for p in input_paths:
|
@@ -47,6 +47,7 @@ class ProjectController:
|
|
47
47
|
self.exclude_patterns: List[str] = (
|
48
48
|
args.exclude if hasattr(args, "exclude") else []
|
49
49
|
)
|
50
|
+
self.changes_only: bool = getattr(args, "changes", False)
|
50
51
|
else:
|
51
52
|
self.args = None
|
52
53
|
self.input_paths = []
|
@@ -54,6 +55,7 @@ class ProjectController:
|
|
54
55
|
self.structure_only = False
|
55
56
|
self.include_patterns = []
|
56
57
|
self.exclude_patterns = []
|
58
|
+
self.changes_only = False
|
57
59
|
self.file_tree: Optional[FileTree] = None
|
58
60
|
self.gitignore_patterns: List[str] = []
|
59
61
|
if self.root_dir:
|
@@ -109,13 +111,23 @@ class ProjectController:
|
|
109
111
|
return files
|
110
112
|
|
111
113
|
def collect_file_tree(self) -> None:
|
112
|
-
|
114
|
+
if self.changes_only:
|
115
|
+
self.logger.info("Collecting uncommitted files from Git repository.")
|
116
|
+
else:
|
117
|
+
self.logger.info("Collecting files from Git tracked files if available.")
|
113
118
|
try:
|
114
119
|
from reposnap.core.git_repo import GitRepo
|
115
120
|
|
116
121
|
git_repo = GitRepo(self.root_dir)
|
117
|
-
|
118
|
-
|
122
|
+
if self.changes_only:
|
123
|
+
all_files = git_repo.get_uncommitted_files()
|
124
|
+
self.logger.info(
|
125
|
+
"Using only uncommitted files (staged, unstaged, untracked, stashed)."
|
126
|
+
)
|
127
|
+
else:
|
128
|
+
all_files = git_repo.get_git_files()
|
129
|
+
self.logger.info("Using all Git tracked files.")
|
130
|
+
self.logger.debug(f"Git files: {all_files}")
|
119
131
|
except Exception as e:
|
120
132
|
self.logger.warning(f"Error obtaining Git tracked files: {e}.")
|
121
133
|
all_files = []
|
reposnap/core/git_repo.py
CHANGED
@@ -30,3 +30,71 @@ class GitRepo:
|
|
30
30
|
except InvalidGitRepositoryError:
|
31
31
|
self.logger.error(f"Invalid Git repository at: {self.repo_path}")
|
32
32
|
return []
|
33
|
+
|
34
|
+
def get_uncommitted_files(self) -> List[Path]:
|
35
|
+
"""
|
36
|
+
Return every *working-copy* file that differs from HEAD - staged,
|
37
|
+
unstaged, untracked, plus everything referenced in `git stash list`.
|
38
|
+
Paths are *relative to* self.repo_path.
|
39
|
+
"""
|
40
|
+
try:
|
41
|
+
repo: Repo = Repo(self.repo_path, search_parent_directories=True)
|
42
|
+
repo_root: Path = Path(repo.working_tree_dir).resolve()
|
43
|
+
paths: set = set()
|
44
|
+
|
45
|
+
# Staged changes (diff between index and HEAD)
|
46
|
+
for diff in repo.index.diff("HEAD"):
|
47
|
+
paths.add(diff.a_path or diff.b_path)
|
48
|
+
|
49
|
+
# Unstaged changes (diff between working tree and index)
|
50
|
+
for diff in repo.index.diff(None):
|
51
|
+
paths.add(diff.a_path or diff.b_path)
|
52
|
+
|
53
|
+
# Untracked files
|
54
|
+
paths.update(repo.untracked_files)
|
55
|
+
|
56
|
+
# Stash entries - with performance guard
|
57
|
+
try:
|
58
|
+
stash_refs = repo.git.stash("list", "--format=%gd").splitlines()
|
59
|
+
# Limit stash processing to prevent performance issues
|
60
|
+
max_stashes = 10
|
61
|
+
if len(stash_refs) > max_stashes:
|
62
|
+
self.logger.warning(
|
63
|
+
f"Large stash stack detected ({len(stash_refs)} entries). "
|
64
|
+
f"Processing only the first {max_stashes} stashes."
|
65
|
+
)
|
66
|
+
stash_refs = stash_refs[:max_stashes]
|
67
|
+
|
68
|
+
for ref in stash_refs:
|
69
|
+
if ref.strip(): # Skip empty lines
|
70
|
+
stash_files = repo.git.diff(
|
71
|
+
"--name-only", f"{ref}^1", ref
|
72
|
+
).splitlines()
|
73
|
+
paths.update(stash_files)
|
74
|
+
except Exception as e:
|
75
|
+
self.logger.debug(f"Error processing stash entries: {e}")
|
76
|
+
|
77
|
+
# Convert to relative paths and filter existing files
|
78
|
+
relative_paths = []
|
79
|
+
for path_str in paths:
|
80
|
+
if path_str: # Skip empty strings
|
81
|
+
absolute_path = (repo_root / path_str).resolve()
|
82
|
+
try:
|
83
|
+
relative_path = absolute_path.relative_to(self.repo_path)
|
84
|
+
if absolute_path.is_file():
|
85
|
+
relative_paths.append(relative_path)
|
86
|
+
except ValueError:
|
87
|
+
# Log warning for paths outside repo root
|
88
|
+
self.logger.warning(
|
89
|
+
f"Path {path_str} is outside repository root {self.repo_path}. Skipping."
|
90
|
+
)
|
91
|
+
continue
|
92
|
+
|
93
|
+
# Return sorted, deduplicated list for deterministic output
|
94
|
+
result = sorted(set(relative_paths))
|
95
|
+
self.logger.debug(f"Uncommitted files from {repo_root}: {result}")
|
96
|
+
return result
|
97
|
+
|
98
|
+
except InvalidGitRepositoryError:
|
99
|
+
self.logger.error(f"Invalid Git repository at: {self.repo_path}")
|
100
|
+
return []
|
reposnap/interfaces/cli.py
CHANGED
@@ -40,6 +40,12 @@ def main():
|
|
40
40
|
default=[],
|
41
41
|
help="File/folder patterns to exclude.",
|
42
42
|
)
|
43
|
+
parser.add_argument(
|
44
|
+
"-c",
|
45
|
+
"--changes",
|
46
|
+
action="store_true",
|
47
|
+
help="Use only files that are added/modified/untracked/stashed but not yet committed.",
|
48
|
+
)
|
43
49
|
|
44
50
|
args = parser.parse_args()
|
45
51
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: reposnap
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.0
|
4
4
|
Summary: Generate a Markdown file with all contents of your project
|
5
5
|
Author: agoloborodko
|
6
6
|
License-File: LICENSE
|
@@ -25,6 +25,7 @@ Description-Content-Type: text/markdown
|
|
25
25
|
- **Structure Only Option**: The `--structure-only` flag can be used to generate the Markdown file with just the directory structure, omitting the contents of the files.
|
26
26
|
- **Gitignore Support**: Automatically respects `.gitignore` patterns to exclude files and directories.
|
27
27
|
- **Include and Exclude Patterns**: Use `--include` and `--exclude` to specify patterns for files and directories to include or exclude.
|
28
|
+
- **Changes Only Mode**: Use `-c` or `--changes` to snapshot only uncommitted files (staged, unstaged, untracked, and stashed changes).
|
28
29
|
|
29
30
|
## Installation
|
30
31
|
|
@@ -49,7 +50,7 @@ pip install -r requirements.lock
|
|
49
50
|
To use `reposnap` from the command line, run it with the following options:
|
50
51
|
|
51
52
|
```bash
|
52
|
-
reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] paths [paths ...]
|
53
|
+
reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]] [-c] paths [paths ...]
|
53
54
|
```
|
54
55
|
|
55
56
|
- `paths`: One or more paths (files or directories) within the repository whose content and structure should be rendered.
|
@@ -59,6 +60,7 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]
|
|
59
60
|
- `--debug`: Enable debug-level logging.
|
60
61
|
- `-i, --include`: File/folder patterns to include. For example, `-i "*.py"` includes only Python files.
|
61
62
|
- `-e, --exclude`: File/folder patterns to exclude. For example, `-e "*.md"` excludes all Markdown files.
|
63
|
+
- `-c, --changes`: Use only files that are added/modified/untracked/stashed but not yet committed.
|
62
64
|
|
63
65
|
#### Pattern Matching
|
64
66
|
|
@@ -71,6 +73,42 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]
|
|
71
73
|
- `-i "*.py"`: Includes only files ending with `.py`.
|
72
74
|
- `-e "*.test.*"`: Excludes files with `.test.` in their names.
|
73
75
|
|
76
|
+
#### Only Snapshot Your Current Work
|
77
|
+
|
78
|
+
The `-c` or `--changes` flag allows you to generate documentation for only the files that have been modified but not yet committed. This includes:
|
79
|
+
|
80
|
+
- **Staged changes**: Files that have been added to the index with `git add`
|
81
|
+
- **Unstaged changes**: Files that have been modified but not yet staged
|
82
|
+
- **Untracked files**: New files that haven't been added to Git yet
|
83
|
+
- **Stashed changes**: Files that are stored in Git stash entries
|
84
|
+
|
85
|
+
This is particularly useful when you want to:
|
86
|
+
- Document only your current work-in-progress
|
87
|
+
- Create a snapshot of changes before committing
|
88
|
+
- Review what files you've been working on
|
89
|
+
|
90
|
+
**Examples**:
|
91
|
+
|
92
|
+
1. **Generate documentation for only your uncommitted changes**:
|
93
|
+
```bash
|
94
|
+
reposnap . -c
|
95
|
+
```
|
96
|
+
|
97
|
+
2. **Combine with structure-only for a quick overview**:
|
98
|
+
```bash
|
99
|
+
reposnap . -c --structure-only
|
100
|
+
```
|
101
|
+
|
102
|
+
3. **Filter uncommitted changes by file type**:
|
103
|
+
```bash
|
104
|
+
reposnap . -c -i "*.py"
|
105
|
+
```
|
106
|
+
|
107
|
+
4. **Exclude test files from uncommitted changes**:
|
108
|
+
```bash
|
109
|
+
reposnap . -c -e "*test*"
|
110
|
+
```
|
111
|
+
|
74
112
|
#### Examples
|
75
113
|
|
76
114
|
1. **Generate a full project structure with file contents**:
|
@@ -103,6 +141,12 @@ reposnap [-h] [-o OUTPUT] [--structure-only] [--debug] [-i INCLUDE [INCLUDE ...]
|
|
103
141
|
reposnap my_project/ -e "gui"
|
104
142
|
```
|
105
143
|
|
144
|
+
6. **Document only your current uncommitted work**:
|
145
|
+
|
146
|
+
```bash
|
147
|
+
reposnap . -c
|
148
|
+
```
|
149
|
+
|
106
150
|
### Graphical User Interface
|
107
151
|
|
108
152
|
`reposnap` also provides a GUI for users who prefer an interactive interface.
|
@@ -1,19 +1,19 @@
|
|
1
1
|
reposnap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
reposnap/controllers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
reposnap/controllers/project_controller.py,sha256=
|
3
|
+
reposnap/controllers/project_controller.py,sha256=RROOcb_FiEhFc9oshptPL8moH-5lkUrio7Lp0MqBqp0,10599
|
4
4
|
reposnap/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
reposnap/core/file_system.py,sha256=82gwvmgrsWf63paMrIz-Z0eqIjbqt9_-vujdXlJJoFE,1074
|
6
|
-
reposnap/core/git_repo.py,sha256=
|
6
|
+
reposnap/core/git_repo.py,sha256=YVIbx-Y_MUbnn5Z4E2XBTJbG7Kawx5aUX2tg6vnocd0,4284
|
7
7
|
reposnap/core/markdown_generator.py,sha256=V6uEbxVSbCbxKN9ysTDKsIDvEGBxFutpOpyaZRXZUGw,3747
|
8
8
|
reposnap/interfaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
reposnap/interfaces/cli.py,sha256=
|
9
|
+
reposnap/interfaces/cli.py,sha256=gL0gauEt_AkuRRr-p5YAeHeUPgvZ59lpZMqsopLjHas,1661
|
10
10
|
reposnap/interfaces/gui.py,sha256=sTuQxjD1nPa9FpgfzOwi6VDO5QMMtDX-5CiEhbJJcs4,5429
|
11
11
|
reposnap/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
12
|
reposnap/models/file_tree.py,sha256=jGo_SizdFcOiDC1OOMz-tiijRN3iSD7ENh6Xw8S6OL0,3362
|
13
13
|
reposnap/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
reposnap/utils/path_utils.py,sha256=UrMe5cjspTf-4gjg2lzv6BgLwZ7S_1lLECQvDMDZO9Y,507
|
15
|
-
reposnap-0.
|
16
|
-
reposnap-0.
|
17
|
-
reposnap-0.
|
18
|
-
reposnap-0.
|
19
|
-
reposnap-0.
|
15
|
+
reposnap-0.7.0.dist-info/METADATA,sha256=oJF5qQGPWc6aG2mnupOpiDrbJ1hWbApjDCP0tM-lB2Y,6768
|
16
|
+
reposnap-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
17
|
+
reposnap-0.7.0.dist-info/entry_points.txt,sha256=o3GyO7bpR0dujPCjsvvZMPv4pXNJlFwD49_pA1r5FOA,102
|
18
|
+
reposnap-0.7.0.dist-info/licenses/LICENSE,sha256=Aj7WCYBXi98pvi723HPn4GDRyjxToNWb3PC6j1_lnPk,1069
|
19
|
+
reposnap-0.7.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|