2021年4月14日 星期三

將多個git倉庫合併為一個新的git倉庫

將多個git倉庫合併為一個新的git倉庫

這個可能發生的概率不大,我會遇到是因為在做練習的時候是個別對每個專案追蹤,產生無數個git倉庫,最後整理的時候想一口氣上傳到github備份才想到說這麼多個怎麼處理。

當然最簡單的就是個別把git倉庫移除,也就是直接暴力刪除隱藏資料夾 .git 文件就可以從最外層重建一個倉庫一口氣追蹤全部檔案,不過這樣也會導致之前寫的commit信息不見就是了。

想要保留commit信息,找了很多方法最後是有搞定大概說明一下怎麼做。

以下指令是順著一直做下去的,不要中圖關掉了可能會導致錯誤。


建立模擬倉庫

先說明一下範例檔案,現在作為示範先個別建立幾個子倉庫

樹狀結構長這個樣子

到時候做好的主倉庫也會像這個樣子的結構

git
├── git1
│   └── project1.md
├── git2
│   └── project2.md
└── git3
    └── project3.md

個別在資料夾內提交一個信息模擬要處理的情況

git init
git add *
git commit -m project1的提交信息
cd ..

三個都做或是只做一個可以,剩下的都一樣的指令,改位置而已


建立新的主倉庫

再來建立一個新的倉庫,這個用來當合併所有倉庫後的主倉庫的
我把它取名 git0 位置是在剛剛的樹狀圖 git 資料夾下跟其他git倉庫放一起

主倉庫裡面就放一個 README.md 這個自己建立。直接創一個 新文字文件.txt 也行隨便放點東西。

mkdir git0
cd git0
git init
git add *
git commit -m 初始化總倉庫

好了再來就準備下一步了,現在建好樹狀圖會像這樣

git
├── git0
│   └── README.md
├── git1
│   └── project1.md
├── git2
│   └── project2.md
└── git3
    └── project3.md


在主倉庫新增遠端庫

先新增鏈結並更新

git remote add git1 C:\Users\hunan\Desktop\git\git1
git fetch --all --tags

切到該節點並新增一個分支追蹤

git checkout git1/master
git checkout -b fix1

這裡的分支千萬記得要一開始就建立,分支會紀錄起點在哪裡。到時候合併後的 commmit 紀錄只會追到建立分支之前的提交點。

移動檔案打包到資料夾內

mkdir git1
git mv project1.md git1
git add *
git commit -m 倉庫1打包完成


合併倉庫

合併要分兩個部分,先在支線把檔案打包到新資料夾內,然後在把這個支線rebase到主線,最後在切回主線把這個分支併入。

從分支rebase主線

git rebase master

這裡我開一個新資料夾包進去,就不會有檔案變更的衝突
如果你不是這樣做發生衝突要自己在這裡解決才能下一步

回到主線把剛剛分支合併

git checkout master
git merge fix1 --no-ff

—no-ff 會把當初合併的分支的線路顯示出來,合併後就不是一條直線,而是可以看到過去是一條分支合併進來的。

詳細可以參可底下的圖,那條黃線就是指令弄出來的,沒加就只有一條橘線,個人覺得這樣比較清晰,加不加看情況和自身的習慣。


移除遠端庫和分支

最後就是處理殘留下來的垃圾了,上圖中的 fix1 標記,和最底下的 git1/master 這兩個標記是可以移除的。

git remote remove git1
git branch -d fix1

分支可以留方便記錄,遠端庫是本機的完全沒用建議砍了


結束

好啦至此就完美解決了,線路也很完美漂亮。全部搭完會有一條很完美的長線吧

缺點就是工作量有點大,如果專案太多手動打可能打不完。我現在就處於這個狀況,要不寫個批次檔給他跑,不然就算了…好麻煩,直接放棄紀錄全部重新提交不香嗎QuQ

2021年4月7日 星期三

C 讀寫檔案 範例代碼 [行讀寫.一次讀寫全部 ]

C 讀取檔案 範例代碼

如果是可以使用C++的環境,建議是用C++比較好寫也比較不用擔心指標的問題,C++的範例代碼可以參考這一篇。
https://charlottehong.blogspot.com/2021/04/c.html

寫了兩個版本,一個是單純函式讀取要自己管控字串的記憶體。另一個是類似於封裝成物件的方式自動管理,新手用第二個可能比較好,省去字串管理記憶體的問題,這問題通常是新手出bug的點。

不囉嗦直接上代碼

1. 讀檔的純C函式

使用方式直接輸入檔名就好,會自動讀出來,檔案記得自己創建好。

#include <stdio.h>
#include <stdlib.h>

void ReadFile_Example(const char* name) {
    FILE* pFile;
    long lSize;
    char* buffer;
    long result;

    // 開檔
    if (fopen_s(&pFile, name, "rb") || pFile == NULL) {
        fputs ("File error",stderr); exit (1);
    }
    // 獲取檔案長度
    fseek (pFile , 0 , SEEK_END);
    lSize = ftell (pFile);
    rewind (pFile);
    // 創建所需長度
    buffer = (char*)malloc(sizeof(char)*lSize+1);
    if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}
    buffer[0] = 0;
    // 讀取檔案
    result = fread(buffer, sizeof(char), lSize, pFile);
    if (result != lSize) {fputs ("Reading error",stderr); exit (3);}
    buffer[lSize] = 0;
    printf("%s", buffer);
    // 釋放資源
    fclose (pFile), pFile=NULL;
    free (buffer), buffer=NULL;
}

int main(int argc, char const* argv[]) {
    ReadFile_Example("a.txt");
    return 0;
}

好啦~拿了就用了,沒看過這麼貼心的教學文吧,貼上直接能執行不囉嗦。
下面開始正經解說了。




開檔案 fopen()

FILE* pFile;
// 方法1 (推薦)
fopen_s(&pFile, fileName, "rb");
// 方法2
pFile = fopen(fileName, "rb");

就這樣簡單一行就可以打開了,這邊我用比較新的標準 fopen_s() 而不是 fopen() 差別在於多一個 s 是更安全的意思,詳細在自己google查一下吧,總而言之是一樣的東西寫法有一點點不同而已。

打開之後需要檢查檔案有沒有開成功,失敗的原因有很多,最常見的是檔名打錯。失敗並不會像C++直接噴例外大可無腦不管。

檢測方式直接看他返回的指標是NULL還是有東西就可以了。他可以直接寫在if裡面節省行數,像底下這個樣子。

// 開檔
void openFile(FILE** p, const char* name, const char* mode) {
    if (fopen_s(p, name, mode)) {
        fputs ("Open file error",stderr); exit (1);
    }
}

這邊簡單處理直接噴一個 exit (1) 就不管了,如果開檔失敗還有相對應的例外處理就是從返回值那邊下手,返回布林值1或0表示成功與失敗。

直接 exit(1) 就是一旦失敗就直接結束程式了。返回值是失敗可以有對應方式,重開或是跳過之類的選項。

函式的使用方式像這個樣子

FILE* pFile;
openFile(&pFile, "a.txt", "rb");

獲取檔案長度

讀取的方式也是有依照特定長度讀取 fread( )與內建的行讀取 getline(),如果想用前者一口氣把全部讀完就需要知道檔案有多長了。

計算長度的方式是利用讀取指標計算的,他就像你打開記事本的時候出現的游標一樣意思,一格一格慢慢前進,讀到最後面遇到結束符號為止。

想知道檔案長度就把游標移動到最後面,然後算一下多少格就是了,最後再把檔案移動回原本的位置即可。

long getFileSize(FILE* p) {
    long temp = ftell (p), lSize = 0;
    fseek (p , 0 , SEEK_END);
    (lSize) = ftell (p);
    fseek (p , temp , SEEK_SET);
    return lSize;
}

fseek() 中的是表示偏移距離與相對位置。定位參數總共有3個。

// 從結束點開始計算偏移0格
fseek (p , 0 , SEEK_END);
// 從起點開始計算偏移 temp 格
fseek (p , temp , SEEK_SET); 
// 從當前位置開始計算偏移 -1 格
fseek (p , -1 , SEEK_CUR);

創建動態陣列

也可以用非動態的陣列存,只是檔案大小不可預測,管控動態陣列是不可少的。這邊直接提供一個函式給大家用。

檢測兩次NULL的意思是因為,第一檢測要知道進來的東西有沒有連接著別的動態陣列,要是不檢查直接把原本指的的東西給丟了,直接就 memory leak 了。第二次檢測是要知道系統有沒有真的配發下來,記憶體要是滿了系統是沒辦法給的。

最後再給個結束符,字串的操作千萬記得給一下,沒有給沒辦法正常讀。有所有的字串相關函式都是依靠結束符運作的。

// 創建動態陣列
void createString(char** str, int len) {
    if (*str != NULL) {fputs ("str is not NULL",stderr); exit (2);}
    *str = (char*)malloc(sizeof(char) * len);
    if (*str == NULL) {fputs ("Memory error",stderr); exit (2);}
    *str[0] = 0;
}

要新增就這樣用,這樣就會自動建立了。只是用完後要記得自己釋放。

char* str=NULL;

createString(&str, 10);
printf("%s\n", str);

free(str), str=NULL;

不是太大重點,如果不明白什麼意思,複製貼上就是了。

讀取檔案

讀取的參數 read()

// 讀取檔案
fread(buffer, sizeof(char), lSize, pFile);
buffer[lSize] = 0;
printf("%s", buffer);

buffer 是依據檔案長度建立的字串 char*
lSize 是檔案的總長度

其中間的這一行特別重要

buffer[lSize] = 0;

因為讀檔並不包含字串的結束字符,必須自己設置。估計很多人卡關會卡在這裡,這也引出一個問題是字串的創建長度必須是,檔案長度的+1才可以,還要裝最後一個結束符。

以上就完成拉,想練習可以把三個函式奏起來,組合成一個可以讀檔的程式,當作練習。

打開的參數

簡單整理一下懶人包

模式參數說明
r[讀檔], 有+號時 檔案必須存在不會自動建立
w[寫檔], 有+號時 檔案可以不存在會自動建立新的
b在 Windwos 下必須追加
+可讀可寫
a從尾部追加

其中 b 的意思直接把他當作windwos上必須寫上就好了。不然讀取的時候微軟的換行標記 \r\n 會被讀成只有 \n 導致長度錯了,進而導致可能越界讀到垃圾值。
可以參考這一篇:https://blog.csdn.net/cattylll/article/details/7107089

詳細說明看下面的表格,最常用的就

參數詳細說明

模式參數說明
r開啟檔案進行唯讀,若檔案不存在,則傳回NULL
w開啟檔案進行唯寫,若檔案不存在,則建立新檔,若檔案存在則將之刪除,再建立新檔
a開啟檔案進行附加,若檔案存在,則資料從檔案尾端寫入,若檔案不存在則建立新檔
rb以二進位模式開啟檔案進行唯讀
wb以二進位模式開啟檔案進行唯寫
ab以二進位模式開啟檔案進行附加
r+開啟檔案進行讀寫,若檔案不存在,則建立新檔,若檔案存在,資料將從檔案開頭進行覆寫
w+開啟檔案進行讀寫,若檔案不存在,則建立新檔,若檔案存在則覆寫原有的資料
a+開啟檔案進行附加、讀取,若檔案不存在則建立新檔,若檔案存在,則資料從檔案尾端寫入
r+b以二進位方式開啟檔案進行讀寫
w+b以二進位方式開啟檔案進行讀寫
a+b以二進位方式開啟檔案進行附加、讀取







2. 封裝好的物件

對於新手來說,讀檔最大的難關應該是在字串的操作吧,必須自己手動管理。C是沒有物件的,指標的操作只能自己小心點知道在幹嘛。

我就就大概遵從物件的規則仿製了一個,類物件的操作方式,想練習可以看看我怎麼寫的,照著這個方式去寫程式,寫檔案的函式我留著沒寫,當練習把它寫出來吧。

代碼放最後面,先簡單說明一下怎麼用。

使用方式

宣告變數用這樣的方式

FileData* file = newFileData();

讀取檔案內建會自動管理記憶體直接取就好

FileData_getFileString(file, "a.txt");
printf("%s\n\n", file->data);

最後結束的時候記得釋放資源,在釋放之前是可以連續讀取不同檔案的。

FileData_dtor(file);

代碼

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct FileData{
    FILE* pFile;
    char* data;
    long size;
    long capa;
    bool init;
} FileData;
// 檢查是否由建構子初始化
void FileData_check(FileData* f) {
    if (f->init != true)     {
        fputs ("FileData not init.",stderr); exit (1);
    }
}
// 初始化
FileData* newFileData() {
    FileData* p = (FileData*)malloc(sizeof(FileData));
    if (p == NULL) {
        fputs ("Memory error",stderr); exit (2);
    } else     {
        p->pFile = NULL;
        p->data = NULL;
        p->size = 0;
        p->capa = 0;
        p->init = true;
    }
    return p;
}
// 解構
void FileData_dtor(FileData* f){
    FileData_check(f);
    if (f->pFile != NULL)     {
        fclose (f->pFile);
        f->pFile = NULL;
    }
    if (f->data != NULL)     {
        free (f->data);
        f->data = NULL;
        f->size = 0;
        f->capa = 0;
    }
}
// 讀取檔案到字串
void FileData_getFileString(FileData* self, const char* name) {
    // 檢查
    FileData_check(self);
    if (self->pFile != NULL) {
        fclose (self->pFile);
        self->pFile = NULL;
    }
    // 宣告
    FILE** pFile = &(self->pFile);
    char** buffer = NULL;
    // 開檔
    if (fopen_s(pFile, name, "rb") || !(*pFile)) 
        fputs ("Open file error",stderr),  exit (1);
    // 獲取檔案大小
    fseek (*pFile , 0 , SEEK_END);
    self->size = ftell (*pFile);
    fseek (*pFile , 0 , SEEK_SET);
    // 如果容量不足,重建所需長度
    buffer = &(self->data);
    if (self->capa <= self->size) {
        char* pStr= NULL;
        free(*buffer);
        pStr = (char*)malloc(sizeof(char) * self->size+1);
        self->capa = self->size+1;
        if (pStr == NULL) {fputs ("Memory error",stderr); exit (2);}
        *buffer = pStr;
    }
    fread(*buffer, sizeof(char), self->size, *pFile);
    (*buffer)[self->size] = 0;
    //printf("%s\n", *buffer);
    // 釋放資源
    fclose (*pFile);
}
// 讀取一行到字串
bool FileData_getFileLine(FileData* self, const char* name, long len) {
    // 檢查
    FileData_check(self);
    // 宣告
    FILE** pFile = &(self->pFile);
    char** buffer = NULL;
    // 開檔
    if(self->pFile == NULL){
        if (fopen_s(pFile, name, "rb") || !(*pFile)) 
            fputs ("Open file error",stderr),  exit (1);
    }
    // 獲取檔案大小
    self->size = len;
    // 如果容量不足,重建所需長度
    buffer = &(self->data);
    if (self->capa <= self->size) {
        char* pStr= NULL;
        free(*buffer);
        pStr = (char*)malloc(sizeof(char) * self->size+1);
        self->capa = self->size+1;
        if (pStr == NULL) {fputs ("Memory error",stderr); exit (2);}
        *buffer = pStr;
    }
    if (fgets(*buffer, self->size, *pFile) != NULL) {
        return true;
    }
    // 沒有輸出清空
    *buffer[0] = 0;
    return false;
}
// 覆蓋寫檔案
void FileData_overWrite(FileData* self, const char* name, const char* inStr) {

}

// 測試
void test_getFileString() {
    FileData* file = newFileData();
    FileData_getFileString(file, "b.txt");
    printf("%s\n\n", file->data);
    FileData_getFileString(file, "a.txt");
    printf("%s\n\n", file->data);
    FileData_getFileString(file, "c.txt");
    printf("%s\n\n", file->data);
    FileData_dtor(file);
}
void test_getFileLine() {
    FileData* file = newFileData();

    while(FileData_getFileLine(file, "a.txt", 100)) {
        printf("%s", file->data);
    } printf("\n");

    fseek (file->pFile , 0 , SEEK_SET);
    while(FileData_getFileLine(file, "a.txt", 101)) {
        printf("%s", file->data);
    } printf("\n");

    FileData_dtor(file);
}
//==============================================================
int main(int argc, char const* argv[]) {
    test_getFileString();
    test_getFileLine();
    return 0;
}
//==============================================================

2021年4月5日 星期一

C++ 讀取檔案 範例代碼 (行讀取 空格讀取 一次讀取整個文件)

C++ 讀取檔案 範例代碼 (行讀取 或 空格讀取)

純C的讀取可以看這篇站內文
https://charlottehong.blogspot.com/2021/04/c_7.html


大概是新手時常會遇到的問題,撇除大部分的教學都是C的方式讀取,更推薦用C++的方案讀取坑真的少很多,雖然現在主流教學還是用C讀取覺得很奇怪就是了,整理一篇給大家參考。

先簡單說一下會用到的物件是 fstream 基本已經封裝的超級容易使用了,不需要管指標什麼的直接上手就用就是了。

此文範例包含

  • 逐行讀取
  • 按照空格或跳行讀取
  • 一次性讀取整個文件
  • 按照所需長度讀取到C字串char*
  • 獲取檔案長度

開檔

就很簡單,直接宣告一個物件把檔名打進去就好

string buffer;
fstream fs("a.txt");
fs  >> buffer;

就這樣搞定。容易吧,直接一個箭頭就可以把檔案內容推到 string 裡面去了,只是基 stream 的關係遇到”空格”或是”跳行”就會停下來,所以 buffer 內容只會讀取到該處就停下來了,要繼續往下推才可以。

檢查是不是真的開成功了

這個程序沒讀到檔案也不會報錯,要檢查檔案是不是真的讀到了用 is_open() 檢查,有錯誤就自己噴一個例外標記一下哪行炸了吧。

if (!fs.is_open())
    throw runtime_error("Reading error.");

什麼時候讀完

是否推到檔案結束點,可以用 eof() 判定讀取指標是否到檔案結束標記了,當他回傳 1 的時候就是結束了。

cout << fs.eof() << endl;

所以直接把他放進for迴圈判斷中,讓他自己跑到結尾就可以了

fstream fs(file_name);
for(string str; fs >> str;) {
    cout << str << endl;
}

調整游標位置

檔案讀取是有一個類似游標的東西存在的,比如說你打開一個記事本游標一般就停在0的位置,讀取就是按方向鍵,一直往右邊刷這樣。

開檔的時候可以控制的屬性包含游標位置和權限、游標位置可以選擇起始位置在開頭或是結束處,這取決於你的行為如何,如果你要從檔案結尾處新增文字那當然一開始就設置在結尾處比較合適了。

檔案開啟後還可以調整游標位置,比如說

// 調整游標至,開始位置(beg)開始計算第0個。
fs.seekg(0, ios::beg);
// 調整游標至,當前位置(cur)開始計算第-1個。
fs.seekg(-1, ios::cur);
// 調整游標至,結束位置(end)開始計算第-2個。
fs.seekg(-2, ios::end);

順帶一提一個中文占2個位元,想跳過一個中文字要+2。

權限

就是對檔案是要讀檔還是寫入,參數在檔名之後,什麼都不打預設是可讀可寫。

// 只讀檔
fstream fs(file_name, ios::in);

// 只寫檔
fstream fs(file_name, ios::out);

// 可讀可寫(預設)
fstream fs(file_name, ios::out | ios::out);

還有兩個比較特別的參數 ate 和 app 詳情可以參考這裡。
https://charlottehong.blogspot.com/2017/05/c-iosapp-iosate.html

獲取檔案大小或長度

檔案大小指的其實就是游標最尾端的位置在哪裡,利用剛剛那個移動到最尾端之後回報他的所在位置就可以得到檔案大小了

size_t FileSize(fstream& fs) {
    size_t curr = fs.tellg();
    fs.seekg(0, ios::end);
    size_t len = fs.tellg();
    fs.seekg(curr);
    return len;
};

這是一個封裝好的函式,多寫一個功能是把游標恢復到原本的位置,執行的話像這樣。

fstream fs(file_name, ios::in);
if (!fs.is_open())
    throw runtime_error("Reading error.");

size_t fileSize = FileSize(fs);

size_t 是 unsigned long long 無負號的長長整數。

關閉檔案

結束對檔案的讀取,通常沒用到建議就馬上關了,會出問題的當你的程序不只一個地方讀取檔案的時候,可能會出現同時兩個物件讀同一個檔案,這就不行了。讀取的時候檔案會被占用,不能再有第二個物件來讀取。

fs.close();

順帶一提當物件生命周期結束時會自動關閉檔案的,底下的範例我把讀檔寫在函式內,關檔寫在最後一行其實是可以省略的,因為函式馬上就結束了意味著,物件的生命週期結束了,解構子會自動關閉檔案的,不寫也是合法不會出問題的。(但是我推薦養成習慣寫上,哪天花一個小時Debug最後才發現沒關檔真的會氣死)

最後是讀取到 C 字串

有些人可能一開始寫的代碼是用 char 完成的一時之間實在不轉過來,有兩個解法。一個是直接把 string 轉 char* 就好了,這很容易有內建函式。


string str="chg";
const char* c_str = str.c_str();

如果要直接轉進去 char* 就是不想用 string 那還是有函式可以處理

// 按照所需長度讀取到C字串char*
void ReadFile_buffer(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    char buffer[256]{};
    size_t readSize = sizeof(buffer);
    fs.read(buffer, readSize);
    cout << buffer << endl;
    fs.close();
}

順帶一提這裡的 read() 是可以換成 getline() 的

範例代碼

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

// 逐行讀取
void ReadFile_line(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");

    for (string str; getline(fs, str);){
        cout << str << endl;
    } fs.seekg(0, fs.beg);
    fs.close();
}

// 按照空格或跳行讀取
void ReadFile_space(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    for(string str; fs >> str;) {
        cout << str << endl;
    } fs.seekg(0, ios::beg);
    fs.close();
}

// 一次性讀取整個文件
void ReadFile_all(const string file_name){
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    string str(
        (istreambuf_iterator<char>(fs)),
        istreambuf_iterator<char>()
    );
    cout << str << endl;
    fs.close();
}

// 按照所需長度讀取到C字串char*
void ReadFile_buffer(const string file_name) {
    fstream fs(file_name, ios::in);
    if (!fs.is_open())
        throw runtime_error("Reading error.");
    char buffer[256]{};
    size_t readSize = sizeof(buffer);
    fs.getline(buffer, readSize);
    cout << buffer << endl;
    fs.close();
}

// 獲取檔案長度
size_t FileSize(fstream& fs) {
    size_t curr = fs.tellg();
    fs.seekg(0, ios::end);
    size_t len = fs.tellg();
    fs.seekg(curr);
    return len;
};

int main(int argc, char const* argv[]) {
    string filename = "a.txt";

    //ReadFile_line(filename);
    //ReadFile_space(filename);
    ReadFile_all(filename);
    //ReadFile_buffer(filename);

    return 0;
}