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.
@@ -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 [args.path])
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
- self.logger.info("Collecting files from Git tracked files if available.")
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
- all_files = git_repo.get_git_files()
118
- self.logger.debug(f"Git tracked files: {all_files}")
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 []
@@ -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.6.5
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=XranKRoWd1cRZ4cEXmN_YddO-GHREz9t2J0TUJ4mtgs,10053
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=I0AhB6XbABJ-oVGOSkVhSjFjSFfcm6f1VFHGTeuM4gE,1255
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=qkbYAlgZzlxgW8ePIUwd8JSbvnBVSvde-VlVqzMDh7g,1470
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.6.5.dist-info/METADATA,sha256=V85e8nN5tNH_U-W_tludr_nG8CNvhGEDAX34Tz1r8JM,5348
16
- reposnap-0.6.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- reposnap-0.6.5.dist-info/entry_points.txt,sha256=o3GyO7bpR0dujPCjsvvZMPv4pXNJlFwD49_pA1r5FOA,102
18
- reposnap-0.6.5.dist-info/licenses/LICENSE,sha256=Aj7WCYBXi98pvi723HPn4GDRyjxToNWb3PC6j1_lnPk,1069
19
- reposnap-0.6.5.dist-info/RECORD,,
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,,