From f737620179b509907e1ca98cab59dbd747ff32d5 Mon Sep 17 00:00:00 2001 From: "Jiang, Zhiwei" Date: Mon, 26 May 2025 13:18:53 +0800 Subject: [PATCH 1/3] [SYCLomatic] gitdiff2yaml Signed-off-by: Jiang, Zhiwei --- clang/tools/dpct/gitdiff2yaml.cpp | 309 ++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 clang/tools/dpct/gitdiff2yaml.cpp diff --git a/clang/tools/dpct/gitdiff2yaml.cpp b/clang/tools/dpct/gitdiff2yaml.cpp new file mode 100644 index 000000000000..d7c6c1eca9ca --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml.cpp @@ -0,0 +1,309 @@ +//===--- gitdiff2yaml.cpp -------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Usage: +// $ g++ gitdiff2yaml.cpp -o gitdiff2yaml +// $ cd /path/to/your/git/repo +// $ ./gitdiff2yaml +// This will output the clang replacements in YAML format. +// Limitation: +// (1) The workspace and the staging area should be clean before running +// this tool. +// (2) The line ending in the file should be '\n'. +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::string execGitCommand(const std::string &CMD) { + std::array Buffer; + std::unique_ptr Pipe(popen(CMD.c_str(), "r"), pclose); + if (!Pipe) { + throw std::runtime_error("popen() failed!"); + } + + std::string Result; + while (fgets(Buffer.data(), Buffer.size(), Pipe.get()) != nullptr) { + Result += Buffer.data(); + } + return Result; +} + +struct Replacement { + std::string NewFilePath; + std::string OldFilePath; + unsigned Offset = 0; + unsigned Length = 0; + std::string ReplacementText; +}; + +struct HunkContext { + unsigned OldCurrentLine = 0; + bool InHunk = false; + bool FastForward = false; + std::string CurrentNewFilePath; + std::string CurrentOldFilePath; +}; + +bool startsWith(const std::string &Str, const std::string &Prefix) { + return Str.size() >= Prefix.size() && + Str.compare(0, Prefix.size(), Prefix) == 0; +} + +bool parseHunkHeader(const std::string &Line, HunkContext &HC) { + const std::string HunkHeaderPrefix = "@@ -"; + if (!startsWith(Line, HunkHeaderPrefix)) + return false; + + // E.g., + // @@ -0,0 +1,3 @@ + // ^ + // |-- OldEnd + size_t OldEnd = Line.find(' ', HunkHeaderPrefix.size()); + std::string OldPart = + Line.substr(HunkHeaderPrefix.size(), OldEnd - HunkHeaderPrefix.size()); + size_t Comma = OldPart.find(','); + if (Comma == std::string::npos) { + throw std::runtime_error("Invalid hunk header format: " + Line); + } + HC.OldCurrentLine = std::stoi(OldPart.substr(0, Comma)); + return true; +} + +// vec[0] is -1, which is a placeholder. +// vec[i] /*i is the line number (1-based)*/ is the offset at the line +// beginning. +// Assumption: the line ending in the file is '\n'. +std::vector calculateOldOffset(const std::string &OldFileContent) { + std::vector Ret; + Ret.push_back(-1); // Placeholder for Ret[0]. + std::istringstream OldFileStream(OldFileContent); + std::string Line; + unsigned Offset = 0; + + while (std::getline(OldFileStream, Line)) { + Ret.push_back(Offset); + Offset += Line.size() + + 1; // std::getline does not include the newline character. And we + // assume the line ending is '\n'. So add 1 here. + } + + return Ret; +} + +// 1. Assume the line ending in the file is '\n'. +// 2. The pair (---, +++) may occurs multiple times in one hunk, so we use a +// variable to save the delete (-) operation. The continuous delete operations +// are treated as one operation. +// 3. After the delete operation, if the next line is one or more '+' +// operations, we make them as a replace-replacement. If the next line is a +// context line, the delete operation is a delete-replacement. Then clear the +// variable. +// 4. If we meet insertions ('+') when the variable is empty, we treat it as an +// insert-replacement. +void processHunkBody(const std::string &Line, HunkContext &Ctx, + std::vector &Repls, + const std::vector &CurrentOldFileOffset) { + static std::optional< + std::pair> + DeleteInfo; + static std::optional< + std::pair> + AddInfo; + + auto addRepl = [&]() { + Replacement R; + if (DeleteInfo.has_value() && AddInfo.has_value()) { + // replace-replacement + R.OldFilePath = Ctx.CurrentOldFilePath; + R.NewFilePath = Ctx.CurrentNewFilePath; + R.Length = DeleteInfo->second; + R.Offset = CurrentOldFileOffset[DeleteInfo->first]; + R.ReplacementText = AddInfo->second; + DeleteInfo.reset(); + AddInfo.reset(); + } else if (DeleteInfo.has_value()) { + // delete-replacement + R.OldFilePath = Ctx.CurrentOldFilePath; + R.NewFilePath = Ctx.CurrentNewFilePath; + R.Length = DeleteInfo->second; + R.Offset = CurrentOldFileOffset[DeleteInfo->first]; + R.ReplacementText = ""; + DeleteInfo.reset(); + } else if (AddInfo.has_value()) { + // insert-replacement + R.OldFilePath = Ctx.CurrentOldFilePath; + R.NewFilePath = Ctx.CurrentNewFilePath; + R.Length = AddInfo->second.length(); + R.Offset = CurrentOldFileOffset[AddInfo->first]; + R.ReplacementText = AddInfo->second; + AddInfo.reset(); + } + Repls.push_back(R); + }; + + // Hunk end + if (Line.empty()) { + addRepl(); + Ctx.InHunk = false; + return; + } + + switch (Line[0]) { + case ' ': { + addRepl(); + Ctx.OldCurrentLine++; + break; + } + case '-': { + if (!DeleteInfo.has_value()) { + auto Item = std::pair( + Ctx.OldCurrentLine, + Line.length()); // +1 for the newline character, -1 for the + // '-' in the line beginng + DeleteInfo = Item; + } else { + DeleteInfo->second += + (Line.length()); // +1 for the newline character, -1 for the + // '-' in the line beginng + } + Ctx.OldCurrentLine++; + break; + } + case '+': { + if (!AddInfo.has_value()) { + auto Item = std::pair(Ctx.OldCurrentLine, + Line.substr(1) + '\n'); + AddInfo = Item; + } else { + AddInfo->second += (Line.substr(1) + '\n'); + } + break; + } + } +} + +std::vector parseDiff(const std::string &diffOutput, + const std::string &RepoRoot) { + std::vector replacements; + std::istringstream iss(diffOutput); + std::string line; + + HunkContext HC; + std::vector CurrentOldFileOffset; + + while (std::getline(iss, line)) { + if (startsWith(line, "diff --git")) { + HC.FastForward = false; + continue; + } + if (HC.FastForward) + continue; + + if (startsWith(line, "---")) { + HC.CurrentOldFilePath = + line.substr(4) == "/dev/null" ? "/dev/null" : line.substr(6); + std::ifstream FileStream(RepoRoot + "/" + HC.CurrentOldFilePath); + if (!FileStream.is_open()) { + throw std::runtime_error("Failed to open file: " + RepoRoot + "/" + + HC.CurrentOldFilePath); + } + std::stringstream Buffer; + Buffer << FileStream.rdbuf(); + CurrentOldFileOffset = calculateOldOffset(Buffer.str()); + continue; + } + if (startsWith(line, "+++")) { + HC.CurrentNewFilePath = + line.substr(4) == "/dev/null" ? "/dev/null" : line.substr(6); + if (HC.CurrentOldFilePath == "/dev/null" || + HC.CurrentNewFilePath == "/dev/null") { + HC.FastForward = true; + Replacement R; + R.OldFilePath = HC.CurrentOldFilePath; + R.NewFilePath = HC.CurrentNewFilePath; + replacements.emplace_back(R); + } + continue; + } + + if (parseHunkHeader(line, HC)) { + // Hunk start + HC.InHunk = true; + continue; + } + + if (HC.InHunk) { + processHunkBody(line, HC, replacements, CurrentOldFileOffset); + continue; + } + } + + return replacements; +} + +void printYaml(const std::vector &Repls) { + std::cout << "---" << std::endl; + std::cout << "Replacements:" << std::endl; + for (const auto &R : Repls) { + std::cout << " - FilePath: " << "'" << R.OldFilePath << "'" + << std::endl; + std::cout << " Offset: " << R.Offset << std::endl; + std::cout << " Length: " << R.Length << std::endl; + std::cout << " ReplacementText:" << "\"" << R.ReplacementText << "\"" + << std::endl; + std::cout << " - NewFilePath: " << "'" << R.NewFilePath << "'" + << std::endl; + } + std::cout << "..." << std::endl; +} + +} // namespace + +int main(int argc, char *argv[]) { + if (argc != 2) { + std::cerr << "Usage: gitdiff2yaml " << std::endl; + return 1; + } + std::string OldCommitID = argv[1]; + + std::string RepoRoot = execGitCommand("git rev-parse --show-toplevel"); + RepoRoot = RepoRoot.substr(0, RepoRoot.size() - 1); // Remove the last '\n' + + std::string NewCommitID = execGitCommand("git log -1 --format=\"%H\""); + std::string DiffOutput = execGitCommand("git diff " + OldCommitID); + + execGitCommand("git reset --hard " + OldCommitID); + std::vector Repls = parseDiff(DiffOutput, RepoRoot); + + // Erase emtpy replacements + Repls.erase(std::remove_if(Repls.begin(), Repls.end(), + [](Replacement x) { + return (x.NewFilePath == "" && + x.OldFilePath == "" && x.Offset == 0 && + x.Length == 0 && + x.ReplacementText == ""); + }), + Repls.end()); + + printYaml(Repls); + execGitCommand("git reset --hard " + NewCommitID); + + return 0; +} From 900b2ab496088d2e384b8d9ef594515617844430 Mon Sep 17 00:00:00 2001 From: "Jiang, Zhiwei" Date: Mon, 26 May 2025 14:39:12 +0800 Subject: [PATCH 2/3] Add tests Signed-off-by: Jiang, Zhiwei --- clang/tools/dpct/gitdiff2yaml/1.ref.txt | 13 +++ clang/tools/dpct/gitdiff2yaml/2.ref.txt | 8 ++ clang/tools/dpct/gitdiff2yaml/3.ref.txt | 8 ++ clang/tools/dpct/gitdiff2yaml/4.ref.txt | 33 ++++++ .../dpct/{ => gitdiff2yaml}/gitdiff2yaml.cpp | 75 ++++++++----- clang/tools/dpct/gitdiff2yaml/test.py | 101 ++++++++++++++++++ clang/tools/dpct/gitdiff2yaml/tests.zip | Bin 0 -> 92968 bytes 7 files changed, 213 insertions(+), 25 deletions(-) create mode 100644 clang/tools/dpct/gitdiff2yaml/1.ref.txt create mode 100644 clang/tools/dpct/gitdiff2yaml/2.ref.txt create mode 100644 clang/tools/dpct/gitdiff2yaml/3.ref.txt create mode 100644 clang/tools/dpct/gitdiff2yaml/4.ref.txt rename clang/tools/dpct/{ => gitdiff2yaml}/gitdiff2yaml.cpp (83%) create mode 100644 clang/tools/dpct/gitdiff2yaml/test.py create mode 100644 clang/tools/dpct/gitdiff2yaml/tests.zip diff --git a/clang/tools/dpct/gitdiff2yaml/1.ref.txt b/clang/tools/dpct/gitdiff2yaml/1.ref.txt new file mode 100644 index 000000000000..e288c64de91c --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/1.ref.txt @@ -0,0 +1,13 @@ +--- +Replacements: + - FilePath: '/dev/null' + Offset: 0 + Length: 0 + ReplacementText:"" + NewFilePath: 'b.txt' + - FilePath: '/dev/null' + Offset: 0 + Length: 0 + ReplacementText:"" + NewFilePath: 'c.txt' +... diff --git a/clang/tools/dpct/gitdiff2yaml/2.ref.txt b/clang/tools/dpct/gitdiff2yaml/2.ref.txt new file mode 100644 index 000000000000..20d377d8c0ca --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/2.ref.txt @@ -0,0 +1,8 @@ +--- +Replacements: + - FilePath: 'b.txt' + Offset: 0 + Length: 0 + ReplacementText:"" + NewFilePath: '/dev/null' +... diff --git a/clang/tools/dpct/gitdiff2yaml/3.ref.txt b/clang/tools/dpct/gitdiff2yaml/3.ref.txt new file mode 100644 index 000000000000..45125ba6d818 --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/3.ref.txt @@ -0,0 +1,8 @@ +--- +Replacements: + - FilePath: 'b.txt' + Offset: 12 + Length: 4 + ReplacementText:"777\n" + NewFilePath: 'c.txt' +... diff --git a/clang/tools/dpct/gitdiff2yaml/4.ref.txt b/clang/tools/dpct/gitdiff2yaml/4.ref.txt new file mode 100644 index 000000000000..7c53080ae731 --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/4.ref.txt @@ -0,0 +1,33 @@ +--- +Replacements: + - FilePath: 'a.txt' + Offset: 8 + Length: 8 + ReplacementText:"111\n222\n" + NewFilePath: 'a.txt' + - FilePath: 'a.txt' + Offset: 20 + Length: 4 + ReplacementText:"333\n" + NewFilePath: 'a.txt' + - FilePath: 'a.txt' + Offset: 56 + Length: 0 + ReplacementText:"444\n" + NewFilePath: 'a.txt' + - FilePath: 'a.txt' + Offset: 92 + Length: 4 + ReplacementText:"" + NewFilePath: 'a.txt' + - FilePath: 'b.txt' + Offset: 40 + Length: 12 + ReplacementText:"" + NewFilePath: 'b.txt' + - FilePath: 'b.txt' + Offset: 92 + Length: 0 + ReplacementText:"111\n222\n333\n" + NewFilePath: 'b.txt' +... diff --git a/clang/tools/dpct/gitdiff2yaml.cpp b/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp similarity index 83% rename from clang/tools/dpct/gitdiff2yaml.cpp rename to clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp index d7c6c1eca9ca..c6501ae1d71d 100644 --- a/clang/tools/dpct/gitdiff2yaml.cpp +++ b/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp @@ -31,6 +31,8 @@ namespace { +const std::string LineEnd = "\\n"; + std::string execGitCommand(const std::string &CMD) { std::array Buffer; std::unique_ptr Pipe(popen(CMD.c_str(), "r"), pclose); @@ -150,7 +152,7 @@ void processHunkBody(const std::string &Line, HunkContext &Ctx, // insert-replacement R.OldFilePath = Ctx.CurrentOldFilePath; R.NewFilePath = Ctx.CurrentNewFilePath; - R.Length = AddInfo->second.length(); + R.Length = 0; R.Offset = CurrentOldFileOffset[AddInfo->first]; R.ReplacementText = AddInfo->second; AddInfo.reset(); @@ -189,10 +191,10 @@ void processHunkBody(const std::string &Line, HunkContext &Ctx, case '+': { if (!AddInfo.has_value()) { auto Item = std::pair(Ctx.OldCurrentLine, - Line.substr(1) + '\n'); + Line.substr(1) + LineEnd); AddInfo = Item; } else { - AddInfo->second += (Line.substr(1) + '\n'); + AddInfo->second += (Line.substr(1) + LineEnd); } break; } @@ -219,14 +221,16 @@ std::vector parseDiff(const std::string &diffOutput, if (startsWith(line, "---")) { HC.CurrentOldFilePath = line.substr(4) == "/dev/null" ? "/dev/null" : line.substr(6); - std::ifstream FileStream(RepoRoot + "/" + HC.CurrentOldFilePath); - if (!FileStream.is_open()) { - throw std::runtime_error("Failed to open file: " + RepoRoot + "/" + - HC.CurrentOldFilePath); + if (HC.CurrentOldFilePath != "/dev/null") { + std::ifstream FileStream(RepoRoot + "/" + HC.CurrentOldFilePath); + if (!FileStream.is_open()) { + throw std::runtime_error("Failed to open file: " + RepoRoot + "/" + + HC.CurrentOldFilePath); + } + std::stringstream Buffer; + Buffer << FileStream.rdbuf(); + CurrentOldFileOffset = calculateOldOffset(Buffer.str()); } - std::stringstream Buffer; - Buffer << FileStream.rdbuf(); - CurrentOldFileOffset = calculateOldOffset(Buffer.str()); continue; } if (startsWith(line, "+++")) { @@ -258,29 +262,39 @@ std::vector parseDiff(const std::string &diffOutput, return replacements; } -void printYaml(const std::vector &Repls) { - std::cout << "---" << std::endl; - std::cout << "Replacements:" << std::endl; +void printYaml(std::ostream &stream, const std::vector &Repls) { + stream << "---" << std::endl; + stream << "Replacements:" << std::endl; for (const auto &R : Repls) { - std::cout << " - FilePath: " << "'" << R.OldFilePath << "'" - << std::endl; - std::cout << " Offset: " << R.Offset << std::endl; - std::cout << " Length: " << R.Length << std::endl; - std::cout << " ReplacementText:" << "\"" << R.ReplacementText << "\"" - << std::endl; - std::cout << " - NewFilePath: " << "'" << R.NewFilePath << "'" - << std::endl; + stream << " - FilePath: " << "'" << R.OldFilePath << "'" + << std::endl; + stream << " Offset: " << R.Offset << std::endl; + stream << " Length: " << R.Length << std::endl; + stream << " ReplacementText:" << "\"" << R.ReplacementText << "\"" + << std::endl; + stream << " NewFilePath: " << "'" << R.NewFilePath << "'" + << std::endl; } - std::cout << "..." << std::endl; + stream << "..." << std::endl; } } // namespace int main(int argc, char *argv[]) { - if (argc != 2) { - std::cerr << "Usage: gitdiff2yaml " << std::endl; + if (argc != 2 && argc != 4) { + std::cerr << "Usage: gitdiff2yaml [-o outputfile]" + << std::endl; return 1; } + bool OutputToFile = false; + if (argc == 4) { + if (std::string(argv[2]) != "-o") { + std::cerr << "Invalid option: " << argv[2] << std::endl; + return 1; + } + OutputToFile = true; + } + std::string OldCommitID = argv[1]; std::string RepoRoot = execGitCommand("git rev-parse --show-toplevel"); @@ -302,7 +316,18 @@ int main(int argc, char *argv[]) { }), Repls.end()); - printYaml(Repls); + if (OutputToFile) { + std::ofstream OutFile(argv[3]); + if (!OutFile.is_open()) { + std::cerr << "Failed to open output file: " << argv[3] << std::endl; + return 1; + } + printYaml(OutFile, Repls); + OutFile.close(); + } else { + printYaml(std::cout, Repls); + } + execGitCommand("git reset --hard " + NewCommitID); return 0; diff --git a/clang/tools/dpct/gitdiff2yaml/test.py b/clang/tools/dpct/gitdiff2yaml/test.py new file mode 100644 index 000000000000..7c4dc291cb5b --- /dev/null +++ b/clang/tools/dpct/gitdiff2yaml/test.py @@ -0,0 +1,101 @@ +# ===----- test.py --------------------------------------------------------=== # +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===----------------------------------------------------------------------=== # + +from pathlib import Path +import os +import shutil +import subprocess + +def run_command(cmd): + try: + result = subprocess.run( + cmd, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + return result.stdout + except subprocess.CalledProcessError as e: + print(f"Command fail: {e.stderr}") + return None + +def compare_with_diff(ref_file): + result = subprocess.run( + ["diff", "-u", ref_file, "temp.txt"], + capture_output=True, + text=True + ) + + if result.returncode == 0: + return True + else: + print("Found difference:\n" + result.stdout) + return False + +# Clean up previous test directory if it exists +test_dir = Path("tests") +if test_dir.exists() and test_dir.is_dir(): + shutil.rmtree(test_dir) + +# Check if gitdiff2yaml exists +gitdiff2yaml_path = Path("gitdiff2yaml") +if not gitdiff2yaml_path.exists(): + print("gitdiff2yaml does not exist. Please build it first.") + exit(1) + +run_command(["unzip", "tests.zip"]) +os.chdir("tests") + +# Test 1 +os.chdir("1") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../1.ref.txt") +if test1_res: + print("Test 1 passed") +else: + print("Test 1 failed") +os.chdir("..") + +# Test 2 +os.chdir("2") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../2.ref.txt") +if test1_res: + print("Test 2 passed") +else: + print("Test 2 failed") +os.chdir("..") + +# Test 3 +os.chdir("3") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../3.ref.txt") +if test1_res: + print("Test 3 passed") +else: + print("Test 3 failed") +os.chdir("..") + +# Test 4 +os.chdir("4") +old_commit_id = run_command(["git", "rev-parse", "HEAD~1"]).strip() +result = run_command(["../../gitdiff2yaml", old_commit_id, "-o", "temp.txt"]) +test1_res = compare_with_diff("../../4.ref.txt") +if test1_res: + print("Test 4 passed") +else: + print("Test 4 failed") +os.chdir("..") + +# Clean up +os.chdir("..") +shutil.rmtree(test_dir) diff --git a/clang/tools/dpct/gitdiff2yaml/tests.zip b/clang/tools/dpct/gitdiff2yaml/tests.zip new file mode 100644 index 0000000000000000000000000000000000000000..dec688f804e831f90939d1b192432d2f6d40da42 GIT binary patch literal 92968 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%0A%B0wk~k z-jE1R)*ALGP3=s{A!1hAj1Z_!H!ELZs29Vmw8?=z&+qR-X`$(K*v#|KnX_OVObw#rxvvDH!@#ae)xmBrLX2TV4u?O+O<6``AzH;KAdv$B|Jqh{sJZdgeyW+heF%$sgj zrgbL@lg&y8k?3Zn*qa!MZdN*oMmH=bxkQk9=Ryv4BH!H_INsSk6Ryv48H!C*}r$u6zl@21(&C0S*6C<+C zN(T|?X62Nz#E2BL(m^P?Svds!gac{9kp4EqM&U+HG$kI!S#id-S8tZ^%W+nSDY(yX0ee^(t?L8w{z@Kj8e zU{*TWbx591z>d|^7hI8>pkX^bH4O`3F)OK=8wJ7L5_D5gZaOAxnw2*G0IrMfB6|{b z%`Iv%#Zj~JUAnksD@hGu_GnDt^(k+IVz&fUM$O7wb|EZgrA^Pmp<@jc+3^`Y>zPUF zfTCeZ)iyeJ2hiSg_k-!MN$`pocZPO>McXthGuTg5@TV(*QqdpOtn4x?lCEgD6!6z8 zv0rpB%u1um&4JpA-glja!}dumMo1;`Bsah>3_msNF-)SmPGx}nAv8Qh)G%aylEqyhGmPoF5PL; zQuv8Fot-#TulCKX$_l6hywE4=r403e;8TK%^%~G z5!m_)0;<>8R(&9Oj5M1|$sVYSULAa>%=0KdEjX1xIZA5POa@SmN`bM`Dq$)dE-G%jpr0lu!IE9E?zzRzz|&}JQm z>eCX2Pu>K^2fWZR@*P7Rd=JSfAwT%ZO)21mKBLTQAx=_4x=#wo;A`PvK$474zHM^A ze}EZnVYOE%739KWK=s+V$_>yI!so(+80z2yVNI6=@cpNz`FK?Vzv#-#d{7hN zt0(twI*3PiS3OIK@uJ;T2T|zm>W?dkQP}RPgNSr@HR%IlM3%eiARgUaotjOJ7wxV( zh(vc+w{N6HVz{dgBGTQ}id%>g+3u=?h;(;#_I6@Kio5C{6y05Y=AYzHWOvm;D7w2^ z^D}BF++B4LhwiS*yNGef?y7@Gba(ZIJ;X?Kca<8=cr1eVSGv1;buSjJ$i|||8n+U- z@hIJ^L7$yq(^0Pk?ymOPhshG$RVTX+s=GRBKNi5^u2M5M3WD#y(M>_+1DLSsu2Ofs zWLM6>n;I3nhbgrbys7-M&5X1A{t)?nV+&o zy{)(DSvYj8fg(FT5EDEe#C<^uR5UE9+D0d20(TY26#YT1&|5{?yz#hLz&@Ec0;tiy z+f2!eWXd--eaj$CZUo`!>>Jux!z1 z)%y>c1wbs($akCh>{>u|`gfaS80y9NZqp_Qh~6n0E#NPxtC##GrG=)e2azz0&Up^g zM=O{`(~o>JNnrh0T|KBy|7P+WLtXR-I6ri~R9)ja@F}6!uX2h2P6dPi0zS8s)S#^M zg=54f2b?Q|wlMQ6N(JSxw@tvcSX&6yOJAz6A`A2lyimvHAVXc^z<0D_$PYdu)B-Xu z0LB6@Ybr1?6ma0%ALIi*Su{K^n;fPv`|#UClom$5nY_*(AyA!uNS{AUS1-mPeVZKc zB?RrmW3(LPuzPQ_ia_ZYU4EhgVXdgcKHKm0>J;eF^ekH(*nyf1RfC9e~6*NH) zNq$igWbipG=vKpZv&n%y50xX793o$Z+ROo{PX8)&4MV*cUxnJ_z+TB7I!bAwX?jps zvdW;(Vm)x2*2~~>!KV`*@TwJa^1M3i>pVCVRR=f58c)K5|4qq zdN%dJiyztJ8QYxD39LS-t5-~Ibik~~wH*&|m^0ngEBOtUbCj!B%(PKgujIFwyv5a{ z3Y$GiclElEg~_g-gGh8&FK~v~1iGu|AR67(TYZ)ojpgb&h(~wzZk#8^i+1%KM4`KS z7k(s0VY_+`BGO&GS_Q<2ELYD#Ji4nl=`uB5w5#VJ65ZAN_$Dn9!_{*Tk?!i#JVyzwnI07}S%RzQ zWYjD=Xo)JMx>$Cr&i9jW=vV_qc6`RrDFvXz$d0(M1B!+vanzIn Date: Wed, 28 May 2025 14:46:52 +0800 Subject: [PATCH 3/3] Update Signed-off-by: Jiang, Zhiwei --- clang/tools/dpct/gitdiff2yaml/1.ref.txt | 14 +-- clang/tools/dpct/gitdiff2yaml/2.ref.txt | 8 +- clang/tools/dpct/gitdiff2yaml/3.ref.txt | 12 +- clang/tools/dpct/gitdiff2yaml/4.ref.txt | 56 +++++----- .../tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp | 103 ++++++++++++++++-- 5 files changed, 132 insertions(+), 61 deletions(-) diff --git a/clang/tools/dpct/gitdiff2yaml/1.ref.txt b/clang/tools/dpct/gitdiff2yaml/1.ref.txt index e288c64de91c..23273daa15be 100644 --- a/clang/tools/dpct/gitdiff2yaml/1.ref.txt +++ b/clang/tools/dpct/gitdiff2yaml/1.ref.txt @@ -1,13 +1,5 @@ --- -Replacements: - - FilePath: '/dev/null' - Offset: 0 - Length: 0 - ReplacementText:"" - NewFilePath: 'b.txt' - - FilePath: '/dev/null' - Offset: 0 - Length: 0 - ReplacementText:"" - NewFilePath: 'c.txt' +AddFileHunks: + - NewFilePath: 'b.txt' + - NewFilePath: 'c.txt' ... diff --git a/clang/tools/dpct/gitdiff2yaml/2.ref.txt b/clang/tools/dpct/gitdiff2yaml/2.ref.txt index 20d377d8c0ca..6da1bae78ec3 100644 --- a/clang/tools/dpct/gitdiff2yaml/2.ref.txt +++ b/clang/tools/dpct/gitdiff2yaml/2.ref.txt @@ -1,8 +1,4 @@ --- -Replacements: - - FilePath: 'b.txt' - Offset: 0 - Length: 0 - ReplacementText:"" - NewFilePath: '/dev/null' +DeleteFileHunks: + - OldFilePath: 'b.txt' ... diff --git a/clang/tools/dpct/gitdiff2yaml/3.ref.txt b/clang/tools/dpct/gitdiff2yaml/3.ref.txt index 45125ba6d818..7579b9fee24b 100644 --- a/clang/tools/dpct/gitdiff2yaml/3.ref.txt +++ b/clang/tools/dpct/gitdiff2yaml/3.ref.txt @@ -1,8 +1,8 @@ --- -Replacements: - - FilePath: 'b.txt' - Offset: 12 - Length: 4 - ReplacementText:"777\n" - NewFilePath: 'c.txt' +MoveFileHunks: + - FilePath: 'b.txt' + Offset: 12 + Length: 4 + ReplacementText: "777\n" + NewFilePath: 'c.txt' ... diff --git a/clang/tools/dpct/gitdiff2yaml/4.ref.txt b/clang/tools/dpct/gitdiff2yaml/4.ref.txt index 7c53080ae731..58fe43dd35c5 100644 --- a/clang/tools/dpct/gitdiff2yaml/4.ref.txt +++ b/clang/tools/dpct/gitdiff2yaml/4.ref.txt @@ -1,33 +1,27 @@ --- -Replacements: - - FilePath: 'a.txt' - Offset: 8 - Length: 8 - ReplacementText:"111\n222\n" - NewFilePath: 'a.txt' - - FilePath: 'a.txt' - Offset: 20 - Length: 4 - ReplacementText:"333\n" - NewFilePath: 'a.txt' - - FilePath: 'a.txt' - Offset: 56 - Length: 0 - ReplacementText:"444\n" - NewFilePath: 'a.txt' - - FilePath: 'a.txt' - Offset: 92 - Length: 4 - ReplacementText:"" - NewFilePath: 'a.txt' - - FilePath: 'b.txt' - Offset: 40 - Length: 12 - ReplacementText:"" - NewFilePath: 'b.txt' - - FilePath: 'b.txt' - Offset: 92 - Length: 0 - ReplacementText:"111\n222\n333\n" - NewFilePath: 'b.txt' +ModifyFileHunks: + - FilePath: 'a.txt' + Offset: 8 + Length: 8 + ReplacementText: "111\n222\n" + - FilePath: 'a.txt' + Offset: 20 + Length: 4 + ReplacementText: "333\n" + - FilePath: 'a.txt' + Offset: 56 + Length: 0 + ReplacementText: "444\n" + - FilePath: 'a.txt' + Offset: 92 + Length: 4 + ReplacementText: "" + - FilePath: 'b.txt' + Offset: 40 + Length: 12 + ReplacementText: "" + - FilePath: 'b.txt' + Offset: 92 + Length: 0 + ReplacementText: "111\n222\n333\n" ... diff --git a/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp b/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp index c6501ae1d71d..4ed626d1d565 100644 --- a/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp +++ b/clang/tools/dpct/gitdiff2yaml/gitdiff2yaml.cpp @@ -262,17 +262,106 @@ std::vector parseDiff(const std::string &diffOutput, return replacements; } +struct ModifyHunk { + std::string FilePath; + unsigned Offset = 0; + unsigned Length = 0; + std::string ReplacementText; +}; + +struct AddHunk { + std::string NewFilePath; +}; + +struct DeleteHunk { + std::string OldFilePath; +}; + +struct MoveHunk { + std::string FilePath; + unsigned Offset = 0; + unsigned Length = 0; + std::string ReplacementText; + std::string NewFilePath; +}; + void printYaml(std::ostream &stream, const std::vector &Repls) { - stream << "---" << std::endl; - stream << "Replacements:" << std::endl; + std::vector ModifyHunks; + std::vector AddHunks; + std::vector DeleteHunks; + std::vector MoveHunks; + for (const auto &R : Repls) { - stream << " - FilePath: " << "'" << R.OldFilePath << "'" + if (R.OldFilePath == "/dev/null" && R.NewFilePath != "/dev/null") { + // Add replacement + AddHunk AH; + AH.NewFilePath = R.NewFilePath; + AddHunks.push_back(AH); + continue; + } + if (R.OldFilePath != "/dev/null" && R.NewFilePath == "/dev/null") { + // Delete replacement + DeleteHunk DH; + DH.OldFilePath = R.OldFilePath; + DeleteHunks.push_back(DH); + continue; + } + if (R.OldFilePath == R.NewFilePath && R.OldFilePath != "/dev/null") { + // Modify replacement + ModifyHunk MH; + MH.FilePath = R.OldFilePath; + MH.Offset = R.Offset; + MH.Length = R.Length; + MH.ReplacementText = R.ReplacementText; + ModifyHunks.push_back(MH); + continue; + } + if (R.OldFilePath != R.NewFilePath) { + // Move replacement + MoveHunk MH; + MH.FilePath = R.OldFilePath; + MH.Offset = R.Offset; + MH.Length = R.Length; + MH.ReplacementText = R.ReplacementText; + MH.NewFilePath = R.NewFilePath; + MoveHunks.push_back(MH); + continue; + } + throw std::runtime_error("Invalid replacement: " + R.OldFilePath + " -> " + + R.NewFilePath); + } + + stream << "---" << std::endl; + if (!ModifyHunks.empty()) + stream << "ModifyFileHunks:" << std::endl; + for (const auto &H : ModifyHunks) { + stream << " - FilePath: " << "'" << H.FilePath << "'" << std::endl; + stream << " Offset: " << H.Offset << std::endl; + stream << " Length: " << H.Length << std::endl; + stream << " ReplacementText: " << "\"" << H.ReplacementText << "\"" + << std::endl; + } + if (!AddHunks.empty()) + stream << "AddFileHunks:" << std::endl; + for (const auto &H : AddHunks) { + stream << " - NewFilePath: " << "'" << H.NewFilePath << "'" + << std::endl; + } + if (!DeleteHunks.empty()) + stream << "DeleteFileHunks:" << std::endl; + for (const auto &H : DeleteHunks) { + stream << " - OldFilePath: " << "'" << H.OldFilePath << "'" << std::endl; - stream << " Offset: " << R.Offset << std::endl; - stream << " Length: " << R.Length << std::endl; - stream << " ReplacementText:" << "\"" << R.ReplacementText << "\"" + } + if (!MoveHunks.empty()) + stream << "MoveFileHunks:" << std::endl; + for (const auto &H : MoveHunks) { + stream << " - FilePath: " << "'" << H.FilePath << "'" << std::endl; + stream << " Offset: " << H.Offset << std::endl; + stream << " Length: " << H.Length << std::endl; + stream << " ReplacementText: " << "\"" << H.ReplacementText << "\"" << std::endl; - stream << " NewFilePath: " << "'" << R.NewFilePath << "'" + stream << " NewFilePath: " << "'" << H.NewFilePath << "'" << std::endl; } stream << "..." << std::endl;