Skip to content

Commit 2d9c8ba

Browse files
authored
Merge branch 'main' into saumya/test
2 parents 6f0ef21 + 264b825 commit 2d9c8ba

File tree

4 files changed

+56
-55
lines changed

4 files changed

+56
-55
lines changed

OneBranchPipelines/github-ado-sync.yml

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# GitHub-to-ADO Sync Pipeline
22
# Syncs main branch from public GitHub to internal Azure DevOps daily at 5pm IST
3+
#
4+
# SYNC STRATEGY RATIONALE:
5+
# This pipeline uses a "replace-all" approach rather than traditional git merge/rebase because:
6+
# 1. DIVERGENT HISTORY: ADO repository contains commits from early development that don't exist
7+
# in GitHub. These historical commits were made before GitHub became the source of truth.
8+
# 2. AVOIDING CONFLICTS: Standard git operations (merge, rebase, reset --hard) fail when
9+
# repositories have divergent commit histories. Attempting to merge results in conflicts
10+
# that cannot be automatically resolved.
11+
# 3. IMPLEMENTATION: We use 'git fetch + git rm + git checkout' to completely replace ADO's
12+
# working tree with GitHub's files without attempting to reconcile git history. This creates
13+
# a clean sync commit that updates all files to match GitHub exactly.
14+
# 4. CHANGE DETECTION: The pipeline checks if any files actually differ before creating PRs,
15+
# avoiding unnecessary sync operations when repositories are already aligned.
316

417
name: GitHub-Sync-$(Date:yyyyMMdd)$(Rev:.r)
518

@@ -21,25 +34,15 @@ jobs:
2134
vmImage: 'windows-latest'
2235

2336
steps:
24-
- checkout: none
37+
- checkout: self
38+
persistCredentials: true
2539

2640
- task: CmdLine@2
27-
displayName: 'Clone GitHub repo'
41+
displayName: 'Add GitHub remote'
2842
inputs:
29-
script: git clone https://github.com/microsoft/mssql-python.git repo-dir -b main
30-
workingDirectory: $(Agent.TempDirectory)
31-
32-
- task: CmdLine@2
33-
displayName: 'Add Azure DevOps remote'
34-
inputs:
35-
script: git remote add azdo-mirror https://$(System.AccessToken)@sqlclientdrivers.visualstudio.com/mssql-python/_git/mssql-python
36-
workingDirectory: $(Agent.TempDirectory)/repo-dir
37-
38-
- task: CmdLine@2
39-
displayName: 'Fetch ADO repo'
40-
inputs:
41-
script: git fetch azdo-mirror
42-
workingDirectory: $(Agent.TempDirectory)/repo-dir
43+
script: |
44+
git remote add github https://github.com/microsoft/mssql-python.git
45+
git fetch github main
4346
4447
- task: CmdLine@2
4548
displayName: 'Create timestamped sync branch'
@@ -51,26 +54,35 @@ jobs:
5154
set SYNC_BRANCH=github-sync-%TIMESTAMP%
5255
echo %SYNC_BRANCH% > branchname.txt
5356
echo Creating sync branch: %SYNC_BRANCH%
54-
git fetch azdo-mirror
55-
git show-ref --verify --quiet refs/remotes/azdo-mirror/main
56-
if %ERRORLEVEL% EQU 0 (
57-
git checkout -b %SYNC_BRANCH% -t azdo-mirror/main
58-
) else (
59-
echo azdo-mirror/main does not exist. Exiting.
60-
exit /b 1
61-
)
57+
git checkout -b %SYNC_BRANCH%
6258
echo ##vso[task.setvariable variable=SYNC_BRANCH;isOutput=true]%SYNC_BRANCH%
63-
workingDirectory: $(Agent.TempDirectory)/repo-dir
6459
6560
- task: CmdLine@2
66-
displayName: 'Reset branch to match GitHub main exactly'
61+
displayName: 'Sync with GitHub main'
6762
inputs:
6863
script: |
69-
git -c user.email="sync@microsoft.com" -c user.name="ADO Sync Bot" reset --hard origin/main
70-
workingDirectory: $(Agent.TempDirectory)/repo-dir
64+
echo Syncing with GitHub main...
65+
git config user.email "sync@microsoft.com"
66+
git config user.name "ADO Sync Bot"
67+
68+
git fetch github main
69+
git rm -rf .
70+
git checkout github/main -- .
71+
echo timestamp.txt >> .git\info\exclude
72+
echo branchname.txt >> .git\info\exclude
73+
git diff --cached --quiet
74+
if %ERRORLEVEL% EQU 0 (
75+
echo No changes detected. Skipping commit.
76+
echo ##vso[task.setvariable variable=HAS_CHANGES]false
77+
) else (
78+
echo Changes detected. Creating commit...
79+
git add . && git commit -m "Sync from GitHub main"
80+
echo ##vso[task.setvariable variable=HAS_CHANGES]true
81+
)
7182
7283
- task: CmdLine@2
7384
displayName: 'Push branch to Azure DevOps'
85+
condition: eq(variables['HAS_CHANGES'], 'true')
7486
inputs:
7587
script: |
7688
set /p SYNC_BRANCH=<branchname.txt
@@ -79,18 +91,18 @@ jobs:
7991
git config user.email "sync@microsoft.com"
8092
git config user.name "ADO Sync Bot"
8193
82-
git push azdo-mirror %SYNC_BRANCH% --set-upstream
94+
git push origin %SYNC_BRANCH% --set-upstream
8395
8496
if %ERRORLEVEL% EQU 0 (
8597
echo Branch pushed successfully!
8698
) else (
8799
echo ERROR: Push failed!
88100
exit /b 1
89101
)
90-
workingDirectory: $(Agent.TempDirectory)/repo-dir
91102
92103
- task: CmdLine@2
93104
displayName: 'Create pull request'
105+
condition: eq(variables['HAS_CHANGES'], 'true')
94106
inputs:
95107
script: |
96108
echo Installing Azure DevOps extension...
@@ -124,4 +136,3 @@ jobs:
124136
125137
if exist timestamp.txt del timestamp.txt
126138
if exist branchname.txt del branchname.txt
127-
workingDirectory: $(Agent.TempDirectory)/repo-dir

mssql_python/connection_string_builder.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,9 @@ def _escape_value(self, value: str) -> str:
7777
"""
7878
Escape a parameter value if it contains special characters.
7979
80-
Per MS-ODBCSTR specification:
8180
- Values containing ';', '{', '}', '=', or spaces should be braced for safety
8281
- '}' inside braced values is escaped as '}}'
83-
- '{' inside braced values is escaped as '{{'
82+
- '{' does not need to be escaped
8483
8584
Args:
8685
value: Parameter value to escape
@@ -95,7 +94,7 @@ def _escape_value(self, value: str) -> str:
9594
>>> builder._escape_value("local;host")
9695
'{local;host}'
9796
>>> builder._escape_value("p}w{d")
98-
'{p}}w{{d}'
97+
'{p}}w{d}'
9998
>>> builder._escape_value("ODBC Driver 18 for SQL Server")
10099
'{ODBC Driver 18 for SQL Server}'
101100
"""
@@ -107,8 +106,9 @@ def _escape_value(self, value: str) -> str:
107106
needs_braces = any(ch in value for ch in ";{}= ")
108107

109108
if needs_braces:
110-
# Escape existing braces by doubling them
111-
escaped = value.replace("}", "}}").replace("{", "{{")
109+
# Escape closing braces by doubling them (ODBC requirement)
110+
# Opening braces do not need to be escaped
111+
escaped = value.replace("}", "}}")
112112
return f"{{{escaped}}}"
113113
else:
114114
return value

mssql_python/connection_string_parser.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Handles ODBC-specific syntax per MS-ODBCSTR specification:
88
- Semicolon-separated key=value pairs
99
- Braced values: {value}
10-
- Escaped braces: }} → }, {{ → {
10+
- Escaped braces: }} → } (only closing braces need escaping)
1111
1212
Parser behavior:
1313
- Validates all key=value pairs
@@ -331,7 +331,7 @@ def _parse_braced_value(self, connection_str: str, start_pos: int) -> Tuple[str,
331331
Braced values:
332332
- Start with '{' and end with '}'
333333
- '}' inside the value is escaped as '}}'
334-
- '{' inside the value is escaped as '{{'
334+
- '{' inside the value does not need escaping
335335
- Can contain semicolons and other special characters
336336
337337
Args:
@@ -366,18 +366,8 @@ def _parse_braced_value(self, connection_str: str, start_pos: int) -> Tuple[str,
366366
# Single '}' means end of braced value
367367
start_pos += 1
368368
return "".join(value), start_pos
369-
elif ch == "{":
370-
# Check if it's an escaped left brace
371-
if start_pos + 1 < str_len and connection_str[start_pos + 1] == "{":
372-
# Escaped left brace: '{{' → '{'
373-
value.append("{")
374-
start_pos += 2
375-
else:
376-
# Single '{' inside braced value - keep it as is
377-
value.append(ch)
378-
start_pos += 1
379369
else:
380-
# Regular character
370+
# Regular character (including '{' which doesn't need escaping per ODBC spec)
381371
value.append(ch)
382372
start_pos += 1
383373

tests/test_010_connection_string_parser.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ def test_parse_braced_value_with_escaped_right_brace(self):
6161
"""Test parsing braced values with escaped }}."""
6262
parser = _ConnectionStringParser()
6363
result = parser._parse("PWD={p}}w{{d}")
64-
assert result == {"pwd": "p}w{d"}
64+
assert result == {"pwd": "p}w{{d"}
6565

6666
def test_parse_braced_value_with_all_escapes(self):
67-
"""Test parsing braced values with both {{ and }} escapes."""
67+
"""Test parsing braced values with }} escape ({{ not an escape sequence)."""
6868
parser = _ConnectionStringParser()
6969
result = parser._parse("Value={test}}{{escape}")
70-
assert result == {"value": "test}{escape"}
70+
assert result == {"value": "test}{{escape"}
7171

7272
def test_parse_empty_value(self):
7373
"""Test that empty value raises error."""
@@ -146,10 +146,10 @@ def test_parse_braced_value_with_left_brace(self):
146146
assert result == {"value": "test{value"}
147147

148148
def test_parse_braced_value_double_left_brace(self):
149-
"""Test parsing braced value with escaped {{ (left brace)."""
149+
"""Test parsing braced value with {{ (not an escape sequence)."""
150150
parser = _ConnectionStringParser()
151151
result = parser._parse("Value={test{{value}")
152-
assert result == {"value": "test{value"}
152+
assert result == {"value": "test{{value"}
153153

154154
def test_parse_unicode_characters(self):
155155
"""Test parsing values with unicode characters."""
@@ -197,7 +197,7 @@ def test_parse_special_characters_in_braced_values(self):
197197

198198
# Multiple special chars including braces
199199
result = parser._parse("Token={Bearer: abc123; Expires={{2024-01-01}}}")
200-
assert result == {"token": "Bearer: abc123; Expires={2024-01-01}"}
200+
assert result == {"token": "Bearer: abc123; Expires={{2024-01-01}"}
201201

202202
def test_parse_numbers_and_symbols_in_passwords(self):
203203
"""Test parsing passwords with various numbers and symbols."""

0 commit comments

Comments
 (0)