︿
Top

2022年10月25日 星期二

行尾結束字元探討(End of Line) PART 2 : Vs Code

前言

在前一篇 行尾結束字元探討 PART 1 : Git, 針對 行尾結束字元(End of Line) 在 Git 上的一些議題, 作了一些討論.

本篇再針對 End of Line 在 Vs Code 上的一些議題, 作一些討論.

經過實測發現, 雖然前一篇提到出現 "warning: in the working copy of '.gitignore', LF will be replaced by CRLF the next time Git touches it" 的訊息, 其實對整個版控並無影響, 只是一個提醒而已; 但這整個過程, 將以前沒有注意到的細節, 作了一次探討; 也第一次在 Ubuntu 上安裝 ASP.NET Core 6.0 SDK 及 Visual Studio Code, 並在 Ubuntu 上進行程式修及版控, 也算是有收穫了.

實際測試

Git 的 core.autocrlf 觀念釐清

  • 1.. 所謂 Git 的 core.autocrlf 只要設定為 true, 不論在 Windows 端或 Ubuntu 端, 由 GitHub 同步回來時, 都會轉成 CRLF.
  • 2.. 所謂 Git 的 core.autocrlf 只要設定為 false, 不論在 Windows 端或 Ubuntu 端, 由 GitHub 同步回來時, 都會是維持 GitHub 上的原始狀況.

步驟概要

  • 1.. 由 dotnet new 產生的 *.proj, Program.cs, *.json 是 UTF8 + CRLF
  • 2.. 由 dotnet new 產生的 HomeController.cs, Views\Home\Index.cshtml 都是 UTF8 with BOM + CRLF
  • 3.. 由 dotnet aspnet-codegenerator controller 指令產生出來的 FriendsController.cs, Views\Friends\Index.cshtml 都是 UTF8 + CRLF
  • 4.. 按 Ctrl + N 開新檔案, 都是 UTF8 + CRLF
  • 5.. 在資料夾按滑鼠右鍵, New C# → Class (例如: City.cs) 會是 UTF8 + LF

結論

以下為筆者的觀察結論, 僅供參考. 或許有誤, 尚請指正.

Windows: CRLF

Windows Ubuntu Windows GitHub Ubuntu
A. autocrlf true false CRLF LF LF
B. autocrlf true true CRLF LF CRLF
C. autocrlf false true CRLF CRLF CRLF
D. autocrlf false false CRLF CRLF CRLF

Windows: LF

Windows Ubuntu Windows GitHub Ubuntu
W. autocrlf true false LF LF LF
X. autocrlf true true LF LF CRLF
Y. autocrlf false true LF LF CRLF
Z. autocrlf false false LF LF LF

  • 1.. 情境A: 預設值
  • 2.. 情境W: Windows 端的 Vs Code, New C# → Class (例如: Region.cs) 會是 UTF8 + LF.
  • 3.. 情境W 的反向: 在 Ubuntu 修改程式, 推送到 GitHub, 再由 Windows 擷取 (fetch), 會造成 Windows 端成為 CRLF. 也就是情境W 最後會演變成為情境A.
  • 4.. Vs Code 在 Windows 端會產生 CRLF 及 LF 這 2 種檔案, 要特別留意.
  • 5.. 筆者個人建議:
    (1) Git: core.autocrlf 可以採預設值, 也就是 Windows 為 true, Ubuntu 為 false. 主要是 Windows 端要檢查 git config --list 結果的 core.autocrlf 是否為 true; 並檢查一下 .gitattributes 是否有設定 * text=true
    (2) Vs Code: EOL (End of Line) 的設定, 最好都是採用跟作業系統相同.
  • 步驟細節:

    • 0.. 檢查 Vs Code 預設的 End of Line 設定
      Vs Code 換行符號設定

    • 1.. 由 dotnet new 產生的 *.proj, Program.cs, *.json 是 UTF8 + CRLF
      Program.cs

    • 2.. 由 dotnet new 產生的 HomeController.cs, Views\Home\Index.cshtml 都是 UTF8 with BOM + CRLF
      HomeController.cs

    • 3.. 由 dotnet aspnet-codegenerator controller 指令產生出來的 FriendsController.cs, Views\Friends\Index.cshtml 都是 UTF8 + CRLF
      如下圖, 細節過程, 請參考 [[附錄一]] FriendsController

    • 4.. 按 Ctrl + N 開新檔案, 都是 UTF8 + CRLF
      Untitled-1

    • 5.. 在資料夾按滑鼠右鍵, New C# → Class (例如: City.cs) 會是 UTF8 + LF
      add class Friend.cs 重要: 經實測, 在 ubuntu 的 vs code 進行修改後, 傳至 GitHub, 再同步至 windows 10, 會變成 UTF8 + CRLF

    附錄一: dotnet aspnet-codegenerator controller 指令細節過程

    (1) 安裝必要的 nuget 套件

    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb> dotnet add package Microsoft.EntityFrameworkCore.SqlServer
    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb> dotnet add package Microsoft.EntityFrameworkCore.Design
    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb> dotnet add package  Microsoft.VisualStudio.Web.CodeGeneration.Design
    

    (2) 新增 Models\Friend.cs

    namespace AutoCrLfTrueWeb.Models
    {
        public class Friend
        {
            public int Id { get; set; }
            public string Name { get; set; } = string.Empty;
            public string? Email { get; set; } = null;
            public string? Mobile { get; set; } = null;
        }
    }
    

    (3) 新增 Data\DatabaseContext.cs

    using Microsoft.EntityFrameworkCore;
    using AutoCrLfTrueWeb.Models;
    
    namespace AutoCrLfTrueWeb.Data
    {
     public class DatabaseContext : DbContext
        {
            public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
            {
            }
    
            // [jasper] 要加上 ? 以處理 CS8618 的警告訊息
            public DbSet<Friend>? Friends { get; set; }
    
            // [jasper] 建立初始資料
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                var obj1 = new Friend() { Id=1, Name="Mary", Email="mary@gmail.com", Mobile="123456789"  };
                var obj2 = new Friend() { Id=2, Name="John", Email="john@gmail.com", Mobile="987654321"  };
                var obj3 = new Friend() { Id=3, Name="Jasper", Email="jasper@gmail.com", Mobile="123456987"  };
                modelBuilder.Entity<Friend>().HasData(
                    obj1, obj2, obj3
                );
            }
        }
    }
    

    (4) 在 Program.cs 註冊 DatabaseContext 服務

    using Microsoft.EntityFrameworkCore;
    using MvcFriends.Data;
    
    var builder = WebApplication.CreateBuilder(args);
    // Add services to the container.
    builder.Services.AddControllersWithViews();
    
    // ==(B)========== [jasper] 註冊 DbContext Service ===============
    // 參考 Visual Studio 2022 產出的範例程式
    var connectionString = builder.Configuration.GetConnectionString("SampleContext");
    builder.Services.AddDbContext<DatabaseContext>(options =>
                    options.UseSqlServer(connectionString));
    // ==(E)========== [jasper] 註冊 DbContext Service ===============
    
    //
    var app = builder.Build();
    

    (5) 在 appsettings.json 新增 SampleConnection 資料庫連線

     "code class="language-json""{
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*",
      "ConnectionStrings": {
        "SampleContext": "Data Source=(localdb)\\mssqllocaldb;Database=AutoCrLfTrueDB"
      }  
    }
    

    (6) 建立 Migration

    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb> dotnet build
    MSBuild version 17.3.2+561848881 for .NET
      正在判斷要還原的專案...
      所有專案都在最新狀態,可進行還原。
      AutoCrLfTrueWeb -> D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb\bin\Debug\net6.0\AutoCrLfTrueWeb.dll
    
    建置成功。
        0 個警告
        0 個錯誤
    
    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb> dotnet ef migrations add InitialCreate
    Build started...
    Build succeeded.
    info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
          Entity Framework Core 6.0.10 initialized 'DatabaseContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.10' with options: None
    Done. To undo this action, use 'ef migrations remove'
    

    (7) 建立資料庫

    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb> dotnet ef database update
    Build started...
    Build succeeded.
    info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
          Entity Framework Core 6.0.10 initialized 'DatabaseContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.10' with options: None
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (900ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
          CREATE DATABASE [AutoCrLfTrueDB];
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (531ms) [Parameters=[], CommandType='Text', CommandTimeout='60']
          IF SERVERPROPERTY('EngineEdition') <> 5
          BEGIN
              ALTER DATABASE [AutoCrLfTrueDB] SET READ_COMMITTED_SNAPSHOT ON;
          END;
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (6ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          SELECT 1
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (259ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          CREATE TABLE [__EFMigrationsHistory] (
              [MigrationId] nvarchar(150) NOT NULL,
              [ProductVersion] nvarchar(32) NOT NULL,
              CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
          );
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (42ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          SELECT 1
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (33ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          SELECT OBJECT_ID(N'[__EFMigrationsHistory]');
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          SELECT [MigrationId], [ProductVersion]
          FROM [__EFMigrationsHistory]
          ORDER BY [MigrationId];
    info: Microsoft.EntityFrameworkCore.Migrations[20402]
          Applying migration '20221021070418_InitialCreate'.
    Applying migration '20221021070418_InitialCreate'.
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (35ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          CREATE TABLE [Friends] (
              [Id] int NOT NULL IDENTITY,
              [Name] nvarchar(max) NOT NULL,
              [Email] nvarchar(max) NULL,
              [Mobile] nvarchar(max) NULL,
              CONSTRAINT [PK_Friends] PRIMARY KEY ([Id])
          );
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (402ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Email', N'Mobile', N'Name') AND [object_id] = OBJECT_ID(N'[Friends]'))
              SET IDENTITY_INSERT [Friends] ON;
          INSERT INTO [Friends] ([Id], [Email], [Mobile], [Name])
          VALUES (1, N'mary@gmail.com', N'123456789', N'Mary');
          IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Email', N'Mobile', N'Name') AND [object_id] = OBJECT_ID(N'[Friends]'))
              SET IDENTITY_INSERT [Friends] OFF;
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (14ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Email', N'Mobile', N'Name') AND [object_id] = OBJECT_ID(N'[Friends]'))
              SET IDENTITY_INSERT [Friends] ON;
          INSERT INTO [Friends] ([Id], [Email], [Mobile], [Name])
          VALUES (2, N'john@gmail.com', N'987654321', N'John');
          IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Email', N'Mobile', N'Name') AND [object_id] = OBJECT_ID(N'[Friends]'))
              SET IDENTITY_INSERT [Friends] OFF;
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (16ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Email', N'Mobile', N'Name') AND [object_id] = OBJECT_ID(N'[Friends]'))
              SET IDENTITY_INSERT [Friends] ON;
          INSERT INTO [Friends] ([Id], [Email], [Mobile], [Name])
          VALUES (3, N'jasper@gmail.com', N'123456987', N'Jasper');
          IF EXISTS (SELECT * FROM [sys].[identity_columns] WHERE [name] IN (N'Id', N'Email', N'Mobile', N'Name') AND [object_id] = OBJECT_ID(N'[Friends]'))
              SET IDENTITY_INSERT [Friends] OFF;
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (26ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
          VALUES (N'20221021070418_InitialCreate', N'6.0.10');
    Done.
    

    (8) 連接資料庫, 查看資料
    mssql_localdb_query

    (9) 建立 Controller 及 View

    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb> dotnet aspnet-codegenerator controller --controllerName FriendsController -outDir Controllers -async -namespace AutoCrLfTrueWeb.Controllers -m Friend -dc DatabaseContext -udl
    Building project ...
    Finding the generator 'controller'...
    Running the generator 'controller'...
    
    Minimal hosting scenario!
    Attempting to compile the application in memory.
    Attempting to figure out the EntityFramework metadata for the model and DbContext: 'Friend'
    info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
          Entity Framework Core 6.0.10 initialized 'DatabaseContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.10' with options: None
    Added Controller : '\Controllers\FriendsController.cs'.
    Added View : \Views\Friends\Create.cshtml
    Added View : \Views\Friends\Edit.cshtml
    Added View : \Views\Friends\Details.cshtml
    Added View : \Views\Friends\Delete.cshtml
    Added View : \Views\Friends\Index.cshtml
    RunTime 00:00:32.78
    PS D:\22-Projects.Git\52-ASP.NET Core\GitAutoCrLf\AutoCrLfTrueWeb>
    

    (10) 查看 Controllers\FriendsController.cs 的換行設定
    FriendsController

    參考文件

    1. 基本上, 如果你的專案會同時在不同的作業系統(如 Windows、Linux)上存取, 最好是設定為 true.
    2. 在 checkin 時, Git 會將純文字類型的檔案中的所有 CRLF 字元轉換為 LF, 也就是版本庫中的換行符號一律存成 LF;
    3. 在 checkout 時, 則會將 LF 轉換成目前作業系統的換行符號, 例如在 Windows 上面就是轉成 CRLF.

    先說結論:我不讓 Git 自動轉換任何換行字元,無論是純文字還是二進位檔案.
    Git 的預設行為是自動幫我們處理換行字元的轉換. 為了避免每台機器設定不同而產生互相踩腳的情形, 最好是在建立 repo(檔案庫)時就針對那個 repo 編寫適當的組態檔(.gitAttributes).如此一來, 無論檔案庫被複製到哪裡, 設定都不會跑掉.

    1. 如果依預設值, autocrlf=true.
    2. 當執行 git add 命令時, 文字檔案中出現的 CRLF 斷行字元會自動被轉換成 LF 字元.
    3. 而利用 git checkout 取出檔案到工作目錄時, 則會自動將 LF 字元, 轉換成 CRLF 字元.

    The attributes allow a fine-grained control, how the line endings are converted. Here is an example that will make Git normalize .txt, .vcproj and .sh files, ensure that .vcproj files have CRLF and .sh files have LF in the working directory, and prevent .jpg files from being normalized regardless of their content.

    沒有留言:

    張貼留言