git-p4son 0.1.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.
git_p4son/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """
2
+ git-p4son - Utility for keeping a Perforce workspace and local git repo in sync.
3
+
4
+ This package provides two main commands:
5
+ - sync: Sync local git repository with a Perforce workspace
6
+ - edit: Open local git changes for edit in Perforce
7
+ """
8
+
9
+ __version__ = "0.1.0"
10
+ __author__ = "Andreas Andersson"
11
+ __email__ = "andreas@neoboid.com"
git_p4son/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """
2
+ Entry point for running git-p4son as a module: python -m git_p4son
3
+ """
4
+
5
+ import sys
6
+ from .cli import main
7
+
8
+ if __name__ == '__main__':
9
+ exit_code = main()
10
+ sys.exit(exit_code)
git_p4son/alias.py ADDED
@@ -0,0 +1,162 @@
1
+ """
2
+ Alias command implementation for git-p4son.
3
+ """
4
+
5
+ import argparse
6
+ from .changelist_store import (
7
+ save_changelist_alias,
8
+ list_changelist_aliases,
9
+ delete_changelist_alias,
10
+ )
11
+ from .log import log
12
+
13
+
14
+ def alias_list_command(args: argparse.Namespace) -> int:
15
+ """
16
+ Execute the 'alias list' command.
17
+
18
+ Args:
19
+ args: Parsed command line arguments
20
+
21
+ Returns:
22
+ Exit code (0 for success, non-zero for failure)
23
+ """
24
+ workspace_dir = args.workspace_dir
25
+
26
+ aliases = list_changelist_aliases(workspace_dir)
27
+ if not aliases:
28
+ log.info('No aliases defined')
29
+ return 0
30
+
31
+ for name, changelist in aliases:
32
+ log.info(f'{name} -> {changelist}')
33
+
34
+ return 0
35
+
36
+
37
+ def alias_set_command(args: argparse.Namespace) -> int:
38
+ """
39
+ Execute the 'alias set' command.
40
+
41
+ Args:
42
+ args: Parsed command line arguments
43
+
44
+ Returns:
45
+ Exit code (0 for success, non-zero for failure)
46
+ """
47
+ workspace_dir = args.workspace_dir
48
+
49
+ if not args.changelist.isdigit():
50
+ log.error(f'Invalid changelist number: {args.changelist}')
51
+ return 1
52
+
53
+ if not save_changelist_alias(args.alias, args.changelist,
54
+ workspace_dir, force=args.force):
55
+ return 1
56
+
57
+ log.detail('alias', f'{args.alias} -> {args.changelist}')
58
+ return 0
59
+
60
+
61
+ def alias_delete_command(args: argparse.Namespace) -> int:
62
+ """
63
+ Execute the 'alias delete' command.
64
+
65
+ Args:
66
+ args: Parsed command line arguments
67
+
68
+ Returns:
69
+ Exit code (0 for success, non-zero for failure)
70
+ """
71
+ workspace_dir = args.workspace_dir
72
+
73
+ if not delete_changelist_alias(args.alias, workspace_dir):
74
+ return 1
75
+
76
+ log.info(f'Deleted alias "{args.alias}"')
77
+ return 0
78
+
79
+
80
+ def alias_clean_command(args: argparse.Namespace) -> int:
81
+ """
82
+ Execute the 'alias clean' command with interactive prompts.
83
+
84
+ Args:
85
+ args: Parsed command line arguments
86
+
87
+ Returns:
88
+ Exit code (0 for success, non-zero for failure)
89
+ """
90
+ workspace_dir = args.workspace_dir
91
+
92
+ aliases = list_changelist_aliases(workspace_dir)
93
+ if not aliases:
94
+ log.info('No aliases to clean')
95
+ return 0
96
+
97
+ delete_all = False
98
+ deleted_count = 0
99
+
100
+ for name, changelist in aliases:
101
+ # Interactive output stays as print() — it's a prompt-response UI
102
+ print(f'{name} -> {changelist}')
103
+
104
+ if delete_all:
105
+ delete_changelist_alias(name, workspace_dir)
106
+ deleted_count += 1
107
+ print(f' Deleted')
108
+ continue
109
+
110
+ while True:
111
+ try:
112
+ response = input(
113
+ 'Delete? [y]es / [n]o / [a]ll / [q]uit: ').strip().lower()
114
+ except EOFError:
115
+ print()
116
+ return 0
117
+
118
+ if response in ('y', 'yes'):
119
+ delete_changelist_alias(name, workspace_dir)
120
+ deleted_count += 1
121
+ print(f' Deleted')
122
+ break
123
+ elif response in ('n', 'no'):
124
+ break
125
+ elif response in ('a', 'all'):
126
+ delete_all = True
127
+ delete_changelist_alias(name, workspace_dir)
128
+ deleted_count += 1
129
+ print(f' Deleted')
130
+ break
131
+ elif response in ('q', 'quit'):
132
+ print(f'Deleted {deleted_count} alias(es)')
133
+ return 0
134
+ else:
135
+ print('Please enter y, n, a, or q')
136
+
137
+ print(f'Deleted {deleted_count} alias(es)')
138
+ return 0
139
+
140
+
141
+ def alias_command(args: argparse.Namespace) -> int:
142
+ """
143
+ Dispatch alias subcommands.
144
+
145
+ Args:
146
+ args: Parsed command line arguments
147
+
148
+ Returns:
149
+ Exit code (0 for success, non-zero for failure)
150
+ """
151
+ if args.alias_action == 'list':
152
+ return alias_list_command(args)
153
+ elif args.alias_action == 'set':
154
+ return alias_set_command(args)
155
+ elif args.alias_action == 'delete':
156
+ return alias_delete_command(args)
157
+ elif args.alias_action == 'clean':
158
+ return alias_clean_command(args)
159
+ else:
160
+ log.error(
161
+ 'No alias action specified. Use "git-p4son alias -h" for help.')
162
+ return 1
@@ -0,0 +1,147 @@
1
+ """
2
+ Changelist alias utilities for git-p4son.
3
+
4
+ Stores named aliases for changelist numbers in .git-p4son/changelists/<name>.
5
+ """
6
+
7
+ import os
8
+
9
+ from .log import log
10
+
11
+
12
+ RESERVED_KEYWORDS = frozenset({'latest', 'last-synced', 'branch'})
13
+
14
+
15
+ def _changelists_dir(workspace_dir: str) -> str:
16
+ """Return the path to the changelists alias directory."""
17
+ return os.path.join(workspace_dir, '.git-p4son', 'changelists')
18
+
19
+
20
+ def save_changelist_alias(name: str, changelist: str, workspace_dir: str, force: bool = False) -> bool:
21
+ """
22
+ Save a changelist number under a named alias.
23
+
24
+ Creates .git-p4son/changelists/ directory if needed and writes the
25
+ changelist number to .git-p4son/changelists/<name>.
26
+
27
+ Args:
28
+ name: The alias name
29
+ changelist: The changelist number to store
30
+ workspace_dir: The workspace root directory
31
+ force: If True, overwrite an existing alias file
32
+
33
+ Returns:
34
+ True on success, False on failure
35
+ """
36
+ if name in RESERVED_KEYWORDS:
37
+ log.error(f'Alias name "{name}" is a reserved keyword')
38
+ return False
39
+
40
+ changelists_dir = _changelists_dir(workspace_dir)
41
+ alias_path = os.path.join(changelists_dir, name)
42
+
43
+ if os.path.exists(alias_path) and not force:
44
+ log.error(
45
+ f'Alias "{name}" already exists (use -f/--force to overwrite)')
46
+ return False
47
+
48
+ os.makedirs(changelists_dir, exist_ok=True)
49
+
50
+ with open(alias_path, 'w') as f:
51
+ f.write(changelist + '\n')
52
+
53
+ return True
54
+
55
+
56
+ def load_changelist_alias(name: str, workspace_dir: str) -> str | None:
57
+ """
58
+ Load a changelist number from a named alias.
59
+
60
+ Args:
61
+ name: The alias name
62
+ workspace_dir: The workspace root directory
63
+
64
+ Returns:
65
+ The changelist number string, or None if not found
66
+ """
67
+ alias_path = os.path.join(_changelists_dir(workspace_dir), name)
68
+
69
+ if not os.path.exists(alias_path):
70
+ log.error(f'No changelist alias found: {name}')
71
+ return None
72
+
73
+ with open(alias_path, 'r') as f:
74
+ content = f.read().strip()
75
+
76
+ if not content:
77
+ log.error(f'Changelist alias "{name}" is empty')
78
+ return None
79
+
80
+ return content
81
+
82
+
83
+ def resolve_changelist(value: str, workspace_dir: str) -> str | None:
84
+ """
85
+ Resolve a changelist value that may be a number or a named alias.
86
+
87
+ If the value is all digits, it is returned as-is.
88
+ Otherwise it is looked up as an alias.
89
+
90
+ Args:
91
+ value: A changelist number or alias name
92
+ workspace_dir: The workspace root directory
93
+
94
+ Returns:
95
+ The changelist number string, or None if alias lookup failed
96
+ """
97
+ if value.isdigit():
98
+ return value
99
+ return load_changelist_alias(value, workspace_dir)
100
+
101
+
102
+ def list_changelist_aliases(workspace_dir: str) -> list[tuple[str, str]]:
103
+ """
104
+ Return a list of all changelist aliases and their values.
105
+
106
+ Args:
107
+ workspace_dir: The workspace root directory
108
+
109
+ Returns:
110
+ Sorted list of (alias_name, changelist_number) tuples.
111
+ """
112
+ changelists_dir = _changelists_dir(workspace_dir)
113
+
114
+ if not os.path.isdir(changelists_dir):
115
+ return []
116
+
117
+ aliases = []
118
+ for name in os.listdir(changelists_dir):
119
+ alias_path = os.path.join(changelists_dir, name)
120
+ if os.path.isfile(alias_path):
121
+ with open(alias_path, 'r') as f:
122
+ content = f.read().strip()
123
+ if content:
124
+ aliases.append((name, content))
125
+
126
+ return sorted(aliases, key=lambda x: x[0])
127
+
128
+
129
+ def delete_changelist_alias(name: str, workspace_dir: str) -> bool:
130
+ """
131
+ Delete a changelist alias file.
132
+
133
+ Args:
134
+ name: The alias name to delete
135
+ workspace_dir: The workspace root directory
136
+
137
+ Returns:
138
+ True on success, False if not found
139
+ """
140
+ alias_path = os.path.join(_changelists_dir(workspace_dir), name)
141
+
142
+ if not os.path.exists(alias_path):
143
+ log.error(f'No changelist alias found: {name}')
144
+ return False
145
+
146
+ os.remove(alias_path)
147
+ return True