我如何从 TFVC 回购中挑选到 Git 回购?
How can I cherry pick from a TFVC repo to Git repo?
我们刚刚从 TFVC 迁移到 Git,我们立即遇到了一个问题 - 如何挑选 TFVC 提交到 Git?
给定
- TFVC 分支
$/Alice
- TFVC 分支
$/Bob
- Git 回购
$/Alice
迁移为 alice
分支和 $/Bob
- 作为 bob
分支。
- TFVC 历史未迁移,因此“$/Alice”的整个 TFVC 历史只是一个 Git 提交。
$\Bob
. 也是如此
问题
现在我们发现 $/Alice
中的一个 TFVC 提交在迁移之前没有合并到 $/Bob
。现在在迁移之后我们意识到我们需要将它放在 bob
分支中。真可惜。
我说的是一个很大的变化 - 许多文件。因此,手动比较文件并复制更改不是很可行。我需要尽可能地自动化这个过程。
到目前为止我做了什么
我想我应该为有问题的 TFVC 变更集创建一个补丁。所以,这是代码(假设我需要挑选提交 123):
$files = (tf changeset /noprompt 123 | sls '$/') -replace '^[^$]+',''
$files |% { tf diff /version:C122~C123 /format:unified $_ } >> 123.diff
(我一个文件一个文件地做,因为它比带 /r
标志的 运行 tf diff
快得多)
无论如何,我得到了这样一个补丁文件:
File: BackgroundJobTests\BackgroundJobTests.csproj
===================================================================
--- Server: BackgroundJobTests.csproj;115493
+++ Server: BackgroundJobTests.csproj;389742
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
===================================================================
File: BI\a8i\a8i.csproj
===================================================================
--- Server: a8i.csproj;342293
+++ Server: a8i.csproj;389742
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)$(MSBuildToolsVersion)\Microsoft.Common.props" />
...
典型的 Git 隐藏补丁看起来有点不同:
diff --git a/Yogi.txt b/Yogi.txt
index 056fd9e..1f73d44 100644
--- a/Yogi.txt
+++ b/Yogi.txt
@@ -1 +1 @@
-yaba daba do
+yaba daba doo
diff --git a/hello.txt b/hello.txt
index ce01362..980a0d5 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1 +1 @@
-hello
+Hello World!
在这里我觉得我需要一些指导。也许我做错了,有一个现成的解决方案可以解决我的痛苦。或者也许我的方向是正确的,我所需要的只是一种方法 "fool" Git 接受我的补丁作为隐藏补丁。但是细节决定成败,而我却缺少细节。
我最终得到了以下 Powershell 脚本:
param(
[Parameter(Mandatory = $true, Position = 0)]$SrcBaseDir,
[Parameter(Mandatory = $true, Position = 1)]$SrcRepo,
[Parameter(Mandatory = $true, Position = 2)]$DstBaseDir,
[Parameter(Mandatory = $true, Position = 3)][int]$Changeset)
[io.directory]::SetCurrentDirectory($DstBaseDir)
cd $SrcBaseDir
$files = @((tf changeset /noprompt $Changeset | sls '$/') -replace '^[^$]+','')
Write-Host -ForegroundColor Green "Found $($files.Length) files"
cd $DstBaseDir
$GitStatus = git status --porcelain
$FailedPatches = @{}
$PatchFilePathPrefix = "$env:TEMP$(Get-Date -Format 'yyyyMMddHHmmss')_"
$NotFound = @()
$Modified = @()
$i = 0
$files |% {
++$i
$TargetFile = $_.Substring($SrcRepo.Length)
if (!(Test-Path $TargetFile))
{
Write-Host -ForegroundColor Yellow "[$i] not found skipped $TargetFile"
$NotFound += $TargetFile
}
elseif ($GitStatus | sls -SimpleMatch $TargetFile)
{
# Very important - git status returns wrong result if the case is different
# This is why I pipe it through PowerShell Select-String command which lets me check
# the status case insensitively
Write-Host -ForegroundColor Yellow "[$i] already modified skipped $TargetFile"
$Modified += $TargetFile
}
else
{
Write-Host -ForegroundColor Green "[$i] $TargetFile"
pushd $SrcBaseDir
try
{
$patch = tf diff /version:"C$($Changeset - 1)~C$Changeset" /format:unified $_
}
finally
{
popd
}
$PatchFileName = "${PatchFilePathPrefix}$($TargetFile -replace '[\/]','_').patch"
$patch `
-replace "^--- Server: .*","--- a/$TargetFile" `
-replace "^\+\+\+ Server: .*","+++ b/$TargetFile" | Out-File -Encoding utf8 $PatchFileName
$res = git apply --whitespace=nowarn $PatchFileName 2>&1
$failed = $LASTEXITCODE
if ($failed)
{
$bytes = [io.file]::ReadAllBytes($TargetFile)
$BOMCount = 0
while ($bytes[$BOMCount] -gt 127)
{
++$BOMCount
}
if ($BOMCount)
{
$fs = [io.file]::Create($TargetFile)
$fs.Write($bytes, $BOMCount, $bytes.Length - $BOMCount)
$fs.Close()
$res = git apply --whitespace=nowarn $PatchFileName 2>&1
$failed = $LASTEXITCODE
if ($failed)
{
[io.file]::WriteAllBytes($TargetFile, $bytes)
}
else
{
$NewBytes = [io.file]::ReadAllBytes($TargetFile)
$fs = [io.file]::Create($TargetFile)
$fs.Write($bytes, 0, $BOMCount)
$fs.Write($NewBytes, 0, $NewBytes.Length)
$fs.Close();
}
}
}
if ($failed)
{
$res |% {
if ($_ -is [Management.Automation.ErrorRecord])
{
$_.Exception.Message
}
else
{
$_
}
} | Write-Host -ForegroundColor Red
$FailedPatches[$TargetFile] = $PatchFileName
}
else
{
del $PatchFileName
}
}
}
@{
Failed = $FailedPatches
NotFound = $NotFound
AlreadyModified = $Modified
}
我们刚刚从 TFVC 迁移到 Git,我们立即遇到了一个问题 - 如何挑选 TFVC 提交到 Git?
给定
- TFVC 分支
$/Alice
- TFVC 分支
$/Bob
- Git 回购
$/Alice
迁移为alice
分支和$/Bob
- 作为bob
分支。 - TFVC 历史未迁移,因此“$/Alice”的整个 TFVC 历史只是一个 Git 提交。
$\Bob
. 也是如此
问题
现在我们发现 $/Alice
中的一个 TFVC 提交在迁移之前没有合并到 $/Bob
。现在在迁移之后我们意识到我们需要将它放在 bob
分支中。真可惜。
我说的是一个很大的变化 - 许多文件。因此,手动比较文件并复制更改不是很可行。我需要尽可能地自动化这个过程。
到目前为止我做了什么
我想我应该为有问题的 TFVC 变更集创建一个补丁。所以,这是代码(假设我需要挑选提交 123):
$files = (tf changeset /noprompt 123 | sls '$/') -replace '^[^$]+',''
$files |% { tf diff /version:C122~C123 /format:unified $_ } >> 123.diff
(我一个文件一个文件地做,因为它比带 /r
标志的 运行 tf diff
快得多)
无论如何,我得到了这样一个补丁文件:
File: BackgroundJobTests\BackgroundJobTests.csproj
===================================================================
--- Server: BackgroundJobTests.csproj;115493
+++ Server: BackgroundJobTests.csproj;389742
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
===================================================================
File: BI\a8i\a8i.csproj
===================================================================
--- Server: a8i.csproj;342293
+++ Server: a8i.csproj;389742
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)$(MSBuildToolsVersion)\Microsoft.Common.props" />
...
典型的 Git 隐藏补丁看起来有点不同:
diff --git a/Yogi.txt b/Yogi.txt
index 056fd9e..1f73d44 100644
--- a/Yogi.txt
+++ b/Yogi.txt
@@ -1 +1 @@
-yaba daba do
+yaba daba doo
diff --git a/hello.txt b/hello.txt
index ce01362..980a0d5 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1 +1 @@
-hello
+Hello World!
在这里我觉得我需要一些指导。也许我做错了,有一个现成的解决方案可以解决我的痛苦。或者也许我的方向是正确的,我所需要的只是一种方法 "fool" Git 接受我的补丁作为隐藏补丁。但是细节决定成败,而我却缺少细节。
我最终得到了以下 Powershell 脚本:
param(
[Parameter(Mandatory = $true, Position = 0)]$SrcBaseDir,
[Parameter(Mandatory = $true, Position = 1)]$SrcRepo,
[Parameter(Mandatory = $true, Position = 2)]$DstBaseDir,
[Parameter(Mandatory = $true, Position = 3)][int]$Changeset)
[io.directory]::SetCurrentDirectory($DstBaseDir)
cd $SrcBaseDir
$files = @((tf changeset /noprompt $Changeset | sls '$/') -replace '^[^$]+','')
Write-Host -ForegroundColor Green "Found $($files.Length) files"
cd $DstBaseDir
$GitStatus = git status --porcelain
$FailedPatches = @{}
$PatchFilePathPrefix = "$env:TEMP$(Get-Date -Format 'yyyyMMddHHmmss')_"
$NotFound = @()
$Modified = @()
$i = 0
$files |% {
++$i
$TargetFile = $_.Substring($SrcRepo.Length)
if (!(Test-Path $TargetFile))
{
Write-Host -ForegroundColor Yellow "[$i] not found skipped $TargetFile"
$NotFound += $TargetFile
}
elseif ($GitStatus | sls -SimpleMatch $TargetFile)
{
# Very important - git status returns wrong result if the case is different
# This is why I pipe it through PowerShell Select-String command which lets me check
# the status case insensitively
Write-Host -ForegroundColor Yellow "[$i] already modified skipped $TargetFile"
$Modified += $TargetFile
}
else
{
Write-Host -ForegroundColor Green "[$i] $TargetFile"
pushd $SrcBaseDir
try
{
$patch = tf diff /version:"C$($Changeset - 1)~C$Changeset" /format:unified $_
}
finally
{
popd
}
$PatchFileName = "${PatchFilePathPrefix}$($TargetFile -replace '[\/]','_').patch"
$patch `
-replace "^--- Server: .*","--- a/$TargetFile" `
-replace "^\+\+\+ Server: .*","+++ b/$TargetFile" | Out-File -Encoding utf8 $PatchFileName
$res = git apply --whitespace=nowarn $PatchFileName 2>&1
$failed = $LASTEXITCODE
if ($failed)
{
$bytes = [io.file]::ReadAllBytes($TargetFile)
$BOMCount = 0
while ($bytes[$BOMCount] -gt 127)
{
++$BOMCount
}
if ($BOMCount)
{
$fs = [io.file]::Create($TargetFile)
$fs.Write($bytes, $BOMCount, $bytes.Length - $BOMCount)
$fs.Close()
$res = git apply --whitespace=nowarn $PatchFileName 2>&1
$failed = $LASTEXITCODE
if ($failed)
{
[io.file]::WriteAllBytes($TargetFile, $bytes)
}
else
{
$NewBytes = [io.file]::ReadAllBytes($TargetFile)
$fs = [io.file]::Create($TargetFile)
$fs.Write($bytes, 0, $BOMCount)
$fs.Write($NewBytes, 0, $NewBytes.Length)
$fs.Close();
}
}
}
if ($failed)
{
$res |% {
if ($_ -is [Management.Automation.ErrorRecord])
{
$_.Exception.Message
}
else
{
$_
}
} | Write-Host -ForegroundColor Red
$FailedPatches[$TargetFile] = $PatchFileName
}
else
{
del $PatchFileName
}
}
}
@{
Failed = $FailedPatches
NotFound = $NotFound
AlreadyModified = $Modified
}