aline-ai 0.2.5__py3-none-any.whl → 0.3.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.
- {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/METADATA +3 -1
- aline_ai-0.3.0.dist-info/RECORD +41 -0
- aline_ai-0.3.0.dist-info/entry_points.txt +3 -0
- realign/__init__.py +32 -1
- realign/cli.py +203 -19
- realign/commands/__init__.py +2 -2
- realign/commands/clean.py +149 -0
- realign/commands/config.py +1 -1
- realign/commands/export_shares.py +1785 -0
- realign/commands/hide.py +112 -24
- realign/commands/import_history.py +873 -0
- realign/commands/init.py +104 -217
- realign/commands/mirror.py +131 -0
- realign/commands/pull.py +101 -0
- realign/commands/push.py +155 -245
- realign/commands/review.py +216 -54
- realign/commands/session_utils.py +139 -4
- realign/commands/share.py +965 -0
- realign/commands/status.py +559 -0
- realign/commands/sync.py +91 -0
- realign/commands/undo.py +423 -0
- realign/commands/watcher.py +805 -0
- realign/config.py +21 -10
- realign/file_lock.py +3 -1
- realign/hash_registry.py +310 -0
- realign/hooks.py +368 -384
- realign/logging_config.py +2 -2
- realign/mcp_server.py +263 -549
- realign/mcp_watcher.py +999 -142
- realign/mirror_utils.py +322 -0
- realign/prompts/__init__.py +21 -0
- realign/prompts/presets.py +238 -0
- realign/redactor.py +168 -16
- realign/tracker/__init__.py +9 -0
- realign/tracker/git_tracker.py +1123 -0
- realign/watcher_daemon.py +115 -0
- aline_ai-0.2.5.dist-info/RECORD +0 -28
- aline_ai-0.2.5.dist-info/entry_points.txt +0 -5
- realign/commands/auto_commit.py +0 -231
- realign/commands/commit.py +0 -379
- realign/commands/search.py +0 -449
- realign/commands/show.py +0 -416
- {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/WHEEL +0 -0
- {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.2.5.dist-info → aline_ai-0.3.0.dist-info}/top_level.txt +0 -0
realign/commands/push.py
CHANGED
|
@@ -1,290 +1,200 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
This command provides an interactive workflow:
|
|
6
|
-
1. Run aline review to show unpushed commits
|
|
7
|
-
2. Ask user if they want to hide any commits
|
|
8
|
-
3. If yes, allow interactive hiding
|
|
9
|
-
4. Ask for final confirmation before pushing
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import subprocess
|
|
13
|
-
import sys
|
|
1
|
+
"""Push command - Push session commits to remote repository."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
14
4
|
from pathlib import Path
|
|
15
5
|
from typing import Optional
|
|
16
6
|
|
|
17
|
-
from .
|
|
18
|
-
from .hide import hide_command, parse_commit_indices
|
|
7
|
+
from ..tracker.git_tracker import ReAlignGitTracker
|
|
19
8
|
from ..logging_config import setup_logger
|
|
20
9
|
|
|
21
10
|
logger = setup_logger('realign.commands.push', 'push.log')
|
|
22
11
|
|
|
23
12
|
|
|
24
|
-
def
|
|
13
|
+
def push_command(
|
|
14
|
+
force: bool = False,
|
|
15
|
+
repo_root: Optional[Path] = None
|
|
16
|
+
) -> int:
|
|
25
17
|
"""
|
|
26
|
-
|
|
18
|
+
Push session commits to remote repository.
|
|
19
|
+
|
|
20
|
+
This command pushes your local session commits to the configured
|
|
21
|
+
remote repository, making them available to your team.
|
|
27
22
|
|
|
28
23
|
Args:
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
force: If True, force push (use with caution)
|
|
25
|
+
repo_root: Path to repository root (uses cwd if not provided)
|
|
31
26
|
|
|
32
27
|
Returns:
|
|
33
|
-
|
|
28
|
+
Exit code (0 for success, 1 for error)
|
|
34
29
|
"""
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
# Get project root
|
|
31
|
+
if repo_root is None:
|
|
32
|
+
repo_root = Path(os.getcwd()).resolve()
|
|
33
|
+
|
|
34
|
+
# Initialize tracker
|
|
35
|
+
tracker = ReAlignGitTracker(repo_root)
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
# Check if repository is initialized
|
|
38
|
+
if not tracker.is_initialized():
|
|
39
|
+
print("❌ Repository not initialized")
|
|
40
|
+
print("Run 'aline init' first")
|
|
41
|
+
return 1
|
|
42
|
+
|
|
43
|
+
# Check if remote is configured
|
|
44
|
+
if not tracker.has_remote():
|
|
45
|
+
print("❌ No remote configured")
|
|
46
|
+
print("\nTo set up sharing:")
|
|
47
|
+
print(" aline init --share (browser-based setup)")
|
|
48
|
+
print(" aline share configure (manual configuration)")
|
|
49
|
+
return 1
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
# Get unpushed commits
|
|
52
|
+
unpushed = tracker.get_unpushed_commits()
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
if not unpushed:
|
|
55
|
+
print("✓ All commits are already pushed")
|
|
56
|
+
print(f"\nRemote: {tracker.get_remote_url()}")
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
# Show review of unpushed commits
|
|
60
|
+
print(f"Found {len(unpushed)} unpushed commit(s)\n")
|
|
61
|
+
|
|
62
|
+
# Display commits summary
|
|
63
|
+
try:
|
|
64
|
+
display_commits_summary(tracker, unpushed)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.warning(f"Failed to display commit details: {e}")
|
|
67
|
+
# Just show hashes
|
|
68
|
+
for commit_hash in unpushed:
|
|
69
|
+
print(f" {commit_hash[:8]}")
|
|
70
|
+
|
|
71
|
+
print()
|
|
72
|
+
|
|
73
|
+
# For non-interactive mode, just push
|
|
74
|
+
if force:
|
|
75
|
+
print("Force pushing...")
|
|
76
|
+
success = tracker.safe_push(force=True)
|
|
77
|
+
if success:
|
|
78
|
+
print(f"✓ Successfully force-pushed {len(unpushed)} commit(s)")
|
|
79
|
+
print(f"\nRemote: {tracker.get_remote_url()}")
|
|
80
|
+
return 0
|
|
48
81
|
else:
|
|
49
|
-
print("
|
|
82
|
+
print("❌ Push failed")
|
|
83
|
+
return 1
|
|
50
84
|
|
|
85
|
+
# Interactive mode - prompt user
|
|
86
|
+
print("Push these commit(s)?")
|
|
87
|
+
print(" [Y] Push all")
|
|
88
|
+
print(" [n] Cancel")
|
|
89
|
+
print(" [h] Hide specific commits")
|
|
90
|
+
print()
|
|
51
91
|
|
|
52
|
-
|
|
53
|
-
repo_root: Optional[Path] = None,
|
|
54
|
-
remote: str = "origin",
|
|
55
|
-
branch: Optional[str] = None,
|
|
56
|
-
force: bool = False,
|
|
57
|
-
dry_run: bool = False
|
|
58
|
-
) -> int:
|
|
59
|
-
"""
|
|
60
|
-
Interactive push workflow with review and hide options.
|
|
92
|
+
choice = input("Select [Y/n/h]: ").strip().lower()
|
|
61
93
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
3. Interactive hiding loop (if requested)
|
|
66
|
-
4. Final confirmation
|
|
67
|
-
5. Execute git push
|
|
94
|
+
if choice == 'n':
|
|
95
|
+
print("Cancelled")
|
|
96
|
+
return 0
|
|
68
97
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
force: Skip all confirmation prompts
|
|
74
|
-
dry_run: Don't actually push, just show what would happen
|
|
98
|
+
if choice == 'h':
|
|
99
|
+
# Interactive hide mode
|
|
100
|
+
print("\nEnter commit indices to hide (comma-separated, e.g., 1,3):")
|
|
101
|
+
print("Commits are numbered from the list above")
|
|
75
102
|
|
|
76
|
-
|
|
77
|
-
0 on success, 1 on error, 2 if cancelled by user
|
|
78
|
-
"""
|
|
79
|
-
logger.info("======== Push command started ========")
|
|
103
|
+
indices_str = input("Indices: ").strip()
|
|
80
104
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
["git", "rev-parse", "--show-toplevel"],
|
|
86
|
-
capture_output=True,
|
|
87
|
-
text=True,
|
|
88
|
-
check=True
|
|
89
|
-
)
|
|
90
|
-
repo_root = Path(result.stdout.strip())
|
|
91
|
-
logger.debug(f"Detected repo root: {repo_root}")
|
|
92
|
-
except subprocess.CalledProcessError:
|
|
93
|
-
print("Error: Not in a git repository", file=sys.stderr)
|
|
94
|
-
logger.error("Not in a git repository")
|
|
95
|
-
return 1
|
|
105
|
+
if indices_str:
|
|
106
|
+
try:
|
|
107
|
+
# Parse indices
|
|
108
|
+
indices = [int(i.strip()) for i in indices_str.split(',')]
|
|
96
109
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
result = subprocess.run(
|
|
101
|
-
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
102
|
-
cwd=repo_root,
|
|
103
|
-
capture_output=True,
|
|
104
|
-
text=True,
|
|
105
|
-
check=True
|
|
106
|
-
)
|
|
107
|
-
branch = result.stdout.strip()
|
|
108
|
-
logger.debug(f"Detected current branch: {branch}")
|
|
109
|
-
except subprocess.CalledProcessError:
|
|
110
|
-
print("Error: Failed to detect current branch", file=sys.stderr)
|
|
111
|
-
logger.error("Failed to detect current branch")
|
|
112
|
-
return 1
|
|
110
|
+
# Validate indices
|
|
111
|
+
valid_indices = [i for i in indices if 1 <= i <= len(unpushed)]
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
print("=" * 60 + "\n")
|
|
113
|
+
if not valid_indices:
|
|
114
|
+
print("No valid indices provided, cancelling")
|
|
115
|
+
return 0
|
|
118
116
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
except Exception as e:
|
|
122
|
-
print(f"Error: Failed to get unpushed commits: {e}", file=sys.stderr)
|
|
123
|
-
logger.error(f"Failed to get unpushed commits: {e}", exc_info=True)
|
|
124
|
-
return 1
|
|
117
|
+
# Hide commits using the hide command
|
|
118
|
+
from .hide import hide_command
|
|
125
119
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
logger.info("No unpushed commits found")
|
|
129
|
-
return 0
|
|
120
|
+
# Convert indices back to string format for hide command
|
|
121
|
+
indices_for_hide = ','.join(map(str, valid_indices))
|
|
130
122
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if exit_code != 0:
|
|
134
|
-
logger.error("Review command failed")
|
|
135
|
-
return 1
|
|
123
|
+
if hide_command(indices=indices_for_hide, repo_root=repo_root, force=True) == 0:
|
|
124
|
+
print(f"\n✓ Hidden {len(valid_indices)} commit(s)")
|
|
136
125
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
print("\n" + "=" * 60)
|
|
140
|
-
print("STEP 2: Hide sensitive commits (optional)")
|
|
141
|
-
print("=" * 60 + "\n")
|
|
126
|
+
# Re-check unpushed commits
|
|
127
|
+
unpushed = tracker.get_unpushed_commits()
|
|
142
128
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)
|
|
129
|
+
if not unpushed:
|
|
130
|
+
print("All commits have been hidden")
|
|
131
|
+
return 0
|
|
147
132
|
|
|
148
|
-
|
|
149
|
-
# Interactive hiding loop
|
|
150
|
-
while True:
|
|
151
|
-
print("\nEnter commit indices to hide (e.g., '1', '1,3,5', '2-4', or 'done'):")
|
|
152
|
-
print(" Type 'done' when finished hiding commits")
|
|
153
|
-
print(" Type 'review' to see the current list of commits")
|
|
154
|
-
print(" Type 'cancel' to abort the push operation")
|
|
155
|
-
|
|
156
|
-
user_input = input("\n> ").strip()
|
|
157
|
-
|
|
158
|
-
if not user_input:
|
|
159
|
-
continue
|
|
160
|
-
|
|
161
|
-
if user_input.lower() == 'done':
|
|
162
|
-
break
|
|
163
|
-
elif user_input.lower() == 'cancel':
|
|
164
|
-
print("\n❌ Push cancelled by user.\n")
|
|
165
|
-
logger.info("Push cancelled by user during hide phase")
|
|
166
|
-
return 2
|
|
167
|
-
elif user_input.lower() == 'review':
|
|
168
|
-
print("\n" + "-" * 60)
|
|
169
|
-
review_command(repo_root=repo_root, verbose=False, detect_secrets=False)
|
|
170
|
-
print("-" * 60)
|
|
171
|
-
continue
|
|
172
|
-
|
|
173
|
-
# Try to parse and hide the commits
|
|
174
|
-
try:
|
|
175
|
-
# Validate indices first
|
|
176
|
-
indices_list = parse_commit_indices(user_input)
|
|
177
|
-
|
|
178
|
-
# Refresh commit list
|
|
179
|
-
commits = get_unpushed_commits(repo_root)
|
|
180
|
-
if not commits:
|
|
181
|
-
print("\n✓ No more unpushed commits to hide.\n")
|
|
182
|
-
break
|
|
183
|
-
|
|
184
|
-
max_index = len(commits)
|
|
185
|
-
invalid_indices = [i for i in indices_list if i < 1 or i > max_index]
|
|
186
|
-
if invalid_indices:
|
|
187
|
-
print(f"❌ Invalid indices (out of range 1-{max_index}): {invalid_indices}")
|
|
188
|
-
continue
|
|
189
|
-
|
|
190
|
-
# Execute hide command
|
|
191
|
-
print()
|
|
192
|
-
exit_code = hide_command(
|
|
193
|
-
indices=user_input,
|
|
194
|
-
repo_root=repo_root,
|
|
195
|
-
force=False # Always ask for confirmation
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
if exit_code == 0:
|
|
199
|
-
print("\n✅ Commits hidden successfully.\n")
|
|
200
|
-
logger.info(f"Successfully hidden commits: {user_input}")
|
|
201
|
-
|
|
202
|
-
# Refresh and show updated commit list
|
|
203
|
-
commits = get_unpushed_commits(repo_root)
|
|
204
|
-
if not commits:
|
|
205
|
-
print("✓ No more unpushed commits.\n")
|
|
206
|
-
logger.info("No commits left after hiding")
|
|
207
|
-
return 0
|
|
208
|
-
else:
|
|
209
|
-
print("\n📋 Updated commit list:\n")
|
|
210
|
-
review_command(repo_root=repo_root, verbose=False, detect_secrets=False)
|
|
211
|
-
else:
|
|
212
|
-
print(f"\n❌ Failed to hide commits.\n")
|
|
213
|
-
logger.error(f"Hide command failed for indices: {user_input}")
|
|
214
|
-
|
|
215
|
-
# Ask if user wants to continue
|
|
216
|
-
if not prompt_yes_no("Continue with push workflow?", default=True):
|
|
217
|
-
print("\n❌ Push cancelled.\n")
|
|
218
|
-
return 2
|
|
219
|
-
|
|
220
|
-
except ValueError as e:
|
|
221
|
-
print(f"❌ Invalid input: {e}")
|
|
222
|
-
print(" Examples: '1', '1,3,5', '2-4'")
|
|
223
|
-
continue
|
|
224
|
-
except Exception as e:
|
|
225
|
-
print(f"❌ Error during hide: {e}", file=sys.stderr)
|
|
226
|
-
logger.error(f"Error during hide: {e}", exc_info=True)
|
|
227
|
-
|
|
228
|
-
# Ask if user wants to continue
|
|
229
|
-
if not prompt_yes_no("Continue with push workflow?", default=True):
|
|
230
|
-
print("\n❌ Push cancelled.\n")
|
|
231
|
-
return 2
|
|
232
|
-
|
|
233
|
-
# Step 3: Final confirmation
|
|
234
|
-
if not force:
|
|
235
|
-
print("\n" + "=" * 60)
|
|
236
|
-
print("STEP 3: Final confirmation")
|
|
237
|
-
print("=" * 60 + "\n")
|
|
238
|
-
|
|
239
|
-
# Refresh commit list one more time
|
|
240
|
-
try:
|
|
241
|
-
commits = get_unpushed_commits(repo_root)
|
|
242
|
-
except Exception as e:
|
|
243
|
-
print(f"Error: Failed to refresh commits: {e}", file=sys.stderr)
|
|
244
|
-
return 1
|
|
133
|
+
print(f"\nRemaining {len(unpushed)} commit(s) to push")
|
|
245
134
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
135
|
+
confirm = input("Proceed with push? [Y/n]: ").strip().lower()
|
|
136
|
+
if confirm == 'n':
|
|
137
|
+
print("Cancelled")
|
|
138
|
+
return 0
|
|
139
|
+
else:
|
|
140
|
+
print("❌ Failed to hide commits")
|
|
141
|
+
return 1
|
|
249
142
|
|
|
250
|
-
|
|
143
|
+
except ValueError:
|
|
144
|
+
print("Invalid input format")
|
|
145
|
+
return 1
|
|
251
146
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
logger.info("Push cancelled by user at final confirmation")
|
|
255
|
-
return 2
|
|
147
|
+
# Perform push
|
|
148
|
+
print("\nPushing to remote...")
|
|
256
149
|
|
|
257
|
-
|
|
258
|
-
print("\n" + "=" * 60)
|
|
259
|
-
print("STEP 4: Pushing to remote")
|
|
260
|
-
print("=" * 60 + "\n")
|
|
150
|
+
success = tracker.safe_push(force=force)
|
|
261
151
|
|
|
262
|
-
if
|
|
263
|
-
print(f"
|
|
264
|
-
|
|
152
|
+
if success:
|
|
153
|
+
print(f"✓ Successfully pushed {len(unpushed)} commit(s)")
|
|
154
|
+
print(f"\nRemote: {tracker.get_remote_url()}")
|
|
265
155
|
return 0
|
|
156
|
+
else:
|
|
157
|
+
print("❌ Push failed")
|
|
158
|
+
print("\nTroubleshooting:")
|
|
159
|
+
print(" - Check network connection")
|
|
160
|
+
print(" - Verify remote repository access")
|
|
161
|
+
print(" - Check logs: .realign/logs/push.log")
|
|
162
|
+
return 1
|
|
266
163
|
|
|
267
|
-
try:
|
|
268
|
-
print(f"🚀 Pushing to {remote}/{branch}...")
|
|
269
164
|
|
|
165
|
+
def display_commits_summary(tracker: ReAlignGitTracker, commit_hashes: list):
|
|
166
|
+
"""
|
|
167
|
+
Display a summary of commits to be pushed.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
tracker: ReAlignGitTracker instance
|
|
171
|
+
commit_hashes: List of commit hashes
|
|
172
|
+
"""
|
|
173
|
+
import subprocess
|
|
174
|
+
|
|
175
|
+
for i, commit_hash in enumerate(commit_hashes, 1):
|
|
176
|
+
# Get commit message
|
|
270
177
|
result = subprocess.run(
|
|
271
|
-
["git", "
|
|
272
|
-
cwd=
|
|
178
|
+
["git", "log", "-1", "--format=%s", commit_hash],
|
|
179
|
+
cwd=tracker.realign_dir,
|
|
273
180
|
capture_output=True,
|
|
274
|
-
text=True
|
|
181
|
+
text=True,
|
|
182
|
+
check=False
|
|
275
183
|
)
|
|
276
184
|
|
|
277
|
-
if result.returncode == 0
|
|
278
|
-
print("\n✅ Successfully pushed to remote!\n")
|
|
279
|
-
print(result.stdout)
|
|
280
|
-
logger.info(f"Successfully pushed to {remote}/{branch}")
|
|
281
|
-
return 0
|
|
282
|
-
else:
|
|
283
|
-
print(f"\n❌ Push failed:\n{result.stderr}\n", file=sys.stderr)
|
|
284
|
-
logger.error(f"Git push failed: {result.stderr}")
|
|
285
|
-
return 1
|
|
185
|
+
message = result.stdout.strip() if result.returncode == 0 else "Unknown"
|
|
286
186
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
187
|
+
# Get timestamp
|
|
188
|
+
result = subprocess.run(
|
|
189
|
+
["git", "log", "-1", "--format=%ar", commit_hash],
|
|
190
|
+
cwd=tracker.realign_dir,
|
|
191
|
+
capture_output=True,
|
|
192
|
+
text=True,
|
|
193
|
+
check=False
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
timestamp = result.stdout.strip() if result.returncode == 0 else ""
|
|
197
|
+
|
|
198
|
+
print(f"[{i}] {commit_hash[:8]} - {message}")
|
|
199
|
+
if timestamp:
|
|
200
|
+
print(f" {timestamp}")
|