Skip to content

Commit 59c76b6

Browse files
committed
Merge upstream/dev
1 parent f862b9b commit 59c76b6

7 files changed

Lines changed: 135 additions & 44 deletions

File tree

abort_rebase.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import subprocess
2+
import os
3+
4+
os.chdir(r'C:\Users\Sahil\concore')
5+
6+
# Abort rebase
7+
subprocess.run(['git', 'rebase', '--abort'], capture_output=True)
8+
9+
# Check status
10+
result = subprocess.run(['git', 'status'], capture_output=True, text=True)
11+
print(result.stdout)
12+
print(result.stderr)

complete_push.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import subprocess
2+
import sys
3+
import os
4+
5+
os.chdir(r'C:\Users\Sahil\concore')
6+
7+
print("Aborting rebase and recovering branch state...")
8+
9+
# Method 1: Direct HEAD manipulation
10+
with open('.git/HEAD', 'w') as f:
11+
f.write('ref: refs/heads/feature/enhanced-workflow-validation\n')
12+
13+
# Remove rebase state
14+
import shutil
15+
try:
16+
shutil.rmtree('.git/rebase-merge')
17+
print("✓ Removed rebase-merge directory")
18+
except:
19+
pass
20+
21+
try:
22+
os.remove('.git/REBASE_HEAD')
23+
print("✓ Removed REBASE_HEAD")
24+
except:
25+
pass
26+
27+
# Reset to original HEAD
28+
result = subprocess.run(['git', 'reset', '--hard', 'ad0f393'], capture_output=True, text=True)
29+
print(result.stdout)
30+
if result.stderr:
31+
print(result.stderr)
32+
33+
# Check status
34+
result = subprocess.run(['git', 'status', '--short'], capture_output=True, text=True)
35+
print("\nCurrent status:")
36+
print(result.stdout)
37+
38+
# Fetch upstream
39+
print("\nFetching upstream...")
40+
result = subprocess.run(['git', 'fetch', 'upstream'], capture_output=True, text=True)
41+
if result.returncode != 0:
42+
print(result.stderr)
43+
44+
# Merge upstream/dev
45+
print("\nMerging upstream/dev...")
46+
result = subprocess.run(['git', 'merge', 'upstream/dev', '-m', 'Merge upstream/dev'], capture_output=True, text=True)
47+
print(result.stdout)
48+
if result.returncode != 0:
49+
print("Merge conflicts or error:")
50+
print(result.stderr)
51+
sys.exit(1)
52+
53+
# Push
54+
print("\nPushing to origin...")
55+
result = subprocess.run(['git', 'push', 'origin', 'feature/enhanced-workflow-validation', '--force-with-lease'], capture_output=True, text=True)
56+
print(result.stdout)
57+
if result.returncode != 0:
58+
print(result.stderr)
59+
sys.exit(1)
60+
61+
print("\n✓ Successfully pushed!")

concore_cli/cli.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,13 @@ def run(workflow_file, source, output, type, auto_build):
4747

4848
@cli.command()
4949
@click.argument('workflow_file', type=click.Path(exists=True))
50-
@click.option(
51-
'--source',
52-
'-s',
53-
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
54-
help='Source directory to check file references',
55-
)
50+
@click.option('--source', '-s', default='src', help='Source directory')
5651
def validate(workflow_file, source):
5752
"""Validate a workflow file"""
5853
try:
59-
validate_workflow(workflow_file, console, source)
54+
ok = validate_workflow(workflow_file, source, console)
55+
if not ok:
56+
sys.exit(1)
6057
except Exception as e:
6158
console.print(f"[red]Error:[/red] {str(e)}")
6259
sys.exit(1)

concore_cli/commands/validate.py

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import re
66
import xml.etree.ElementTree as ET
77

8-
def validate_workflow(workflow_file, console, source_dir=None):
8+
def validate_workflow(workflow_file, source_dir, console):
99
workflow_path = Path(workflow_file)
10+
source_root = (workflow_path.parent / source_dir)
1011

1112
console.print(f"[cyan]Validating:[/cyan] {workflow_path.name}")
1213
console.print()
@@ -15,31 +16,35 @@ def validate_workflow(workflow_file, console, source_dir=None):
1516
warnings = []
1617
info = []
1718

19+
def finalize():
20+
show_results(console, errors, warnings, info)
21+
return len(errors) == 0
22+
1823
try:
1924
with open(workflow_path, 'r') as f:
2025
content = f.read()
2126

2227
if not content.strip():
2328
errors.append("File is empty")
24-
return show_results(console, errors, warnings, info)
29+
return finalize()
2530

2631
# strict XML syntax check
2732
try:
2833
ET.fromstring(content)
2934
except ET.ParseError as e:
3035
errors.append(f"Invalid XML: {str(e)}")
31-
return show_results(console, errors, warnings, info)
36+
return finalize()
3237

3338
try:
3439
soup = BeautifulSoup(content, 'xml')
3540
except Exception as e:
3641
errors.append(f"Invalid XML: {str(e)}")
37-
return show_results(console, errors, warnings, info)
42+
return finalize()
3843

3944
root = soup.find('graphml')
4045
if not root:
4146
errors.append("Not a valid GraphML file - missing <graphml> root element")
42-
return show_results(console, errors, warnings, info)
47+
return finalize()
4348

4449
# check the graph attributes
4550
graph = soup.find('graph')
@@ -65,6 +70,9 @@ def validate_workflow(workflow_file, console, source_dir=None):
6570
else:
6671
info.append(f"Found {len(edges)} edge(s)")
6772

73+
if not source_root.exists():
74+
warnings.append(f"Source directory not found: {source_root}")
75+
6876
node_labels = []
6977
for node in nodes:
7078
#check the node id
@@ -84,6 +92,11 @@ def validate_workflow(workflow_file, console, source_dir=None):
8492
label = label_tag.text.strip()
8593
node_labels.append(label)
8694

95+
# reject shell metacharacters to prevent command injection (#251)
96+
if re.search(r'[;&|`$\'"()\\]', label):
97+
errors.append(f"Node '{label}' contains unsafe shell characters")
98+
continue
99+
87100
if ':' not in label:
88101
warnings.append(f"Node '{label}' missing format 'ID:filename'")
89102
else:
@@ -96,6 +109,10 @@ def validate_workflow(workflow_file, console, source_dir=None):
96109
errors.append(f"Node '{label}' has no filename")
97110
elif not any(filename.endswith(ext) for ext in ['.py', '.cpp', '.m', '.v', '.java']):
98111
warnings.append(f"Node '{label}' has unusual file extension")
112+
elif source_root.exists():
113+
file_path = source_root / filename
114+
if not file_path.exists():
115+
errors.append(f"Missing source file: {filename}")
99116
else:
100117
warnings.append(f"Node {node_id} has no label")
101118
except Exception as e:
@@ -138,44 +155,17 @@ def validate_workflow(workflow_file, console, source_dir=None):
138155
if file_edges > 0:
139156
info.append(f"File-based edges: {file_edges}")
140157

141-
if source_dir:
142-
_check_source_files(soup, Path(source_dir), errors, warnings)
143-
144158
_check_cycles(soup, errors, warnings)
145159
_check_zmq_ports(soup, errors, warnings)
146160

147-
show_results(console, errors, warnings, info)
161+
return finalize()
148162

149163
except FileNotFoundError:
150164
console.print(f"[red]Error:[/red] File not found: {workflow_path}")
165+
return False
151166
except Exception as e:
152167
console.print(f"[red]Validation failed:[/red] {str(e)}")
153-
154-
def _check_source_files(soup, source_path, errors, warnings):
155-
nodes = soup.find_all('node')
156-
157-
for node in nodes:
158-
label_tag = node.find('y:NodeLabel') or node.find('NodeLabel')
159-
if not label_tag or not label_tag.text:
160-
continue
161-
162-
label = label_tag.text.strip()
163-
if ':' not in label:
164-
warnings.append(f"Skipping node with invalid label format (expected 'ID:filename')")
165-
continue
166-
167-
parts = label.split(':')
168-
if len(parts) != 2:
169-
warnings.append(f"Skipping node '{label}' with invalid format")
170-
continue
171-
172-
_, filename = parts
173-
if not filename:
174-
continue
175-
176-
file_path = source_path / filename
177-
if not file_path.exists():
178-
errors.append(f"Source file not found: {filename}")
168+
return False
179169

180170
def _check_cycles(soup, errors, warnings):
181171
nodes = soup.find_all('node')

fix_and_push.bat

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@echo off
2+
cd C:\Users\Sahil\concore
3+
rmdir /s /q .git\rebase-merge 2>nul
4+
del /f .git\REBASE_HEAD 2>nul
5+
git reset --hard ad0f393
6+
git fetch upstream
7+
git merge upstream/dev -m "Merge upstream/dev"
8+
git push origin feature/enhanced-workflow-validation --force-with-lease

mkconcore.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ def safe_path(value, context):
101101
raise ValueError(f"Unsafe {context}: '{value}' contains illegal characters.")
102102
return value
103103

104+
def safe_relpath(value, context):
105+
"""
106+
Allow relative subpaths while blocking traversal and absolute/drive paths.
107+
Used for node source file paths that may contain subdirectories.
108+
"""
109+
if not value:
110+
raise ValueError(f"{context} cannot be empty")
111+
normalized = value.replace("\\", "/")
112+
safe_path(normalized, context)
113+
if normalized.startswith("/") or normalized.startswith("~"):
114+
raise ValueError(f"Unsafe {context}: absolute paths are not allowed.")
115+
if re.match(r"^[A-Za-z]:", normalized):
116+
raise ValueError(f"Unsafe {context}: drive paths are not allowed.")
117+
if ":" in normalized:
118+
raise ValueError(f"Unsafe {context}: ':' is not allowed in relative paths.")
119+
if any(part in ("", "..") for part in normalized.split("/")):
120+
raise ValueError(f"Unsafe {context}: invalid path segment.")
121+
return normalized
122+
104123
MKCONCORE_VER = "22-09-18"
105124

106125
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -251,7 +270,8 @@ def _resolve_concore_path():
251270
if ':' in node_label:
252271
container_part, source_part = node_label.split(':', 1)
253272
safe_name(container_part, f"Node container name '{container_part}'")
254-
safe_name(source_part, f"Node source file '{source_part}'")
273+
source_part = safe_relpath(source_part, f"Node source file '{source_part}'")
274+
node_label = f"{container_part}:{source_part}"
255275
else:
256276
safe_name(node_label, f"Node label '{node_label}'")
257277
# Explicitly reject incorrect format to prevent later crashes and ambiguity
@@ -447,6 +467,9 @@ def _resolve_concore_path():
447467
dockername, langext = sourcecode, ""
448468

449469
script_target_path = os.path.join(outdir, "src", sourcecode)
470+
script_target_parent = os.path.dirname(script_target_path)
471+
if script_target_parent:
472+
os.makedirs(script_target_parent, exist_ok=True)
450473

451474
# If the script was specialized, it's already in outdir/src. If not, copy from sourcedir.
452475
if node_id_key not in node_edge_params:

tests/test_graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def test_validate_missing_source_file(self):
163163
result = self.runner.invoke(cli, ['validate', filepath, '--source', str(source_dir)])
164164

165165
self.assertIn('Validation failed', result.output)
166-
self.assertIn('Source file not found: missing.py', result.output)
166+
self.assertIn('Missing source file', result.output)
167167

168168
def test_validate_with_existing_source_file(self):
169169
content = '''

0 commit comments

Comments
 (0)