如何通過 Node.js 克隆和同步 Github 存儲庫
如何在 Node.js 中通過 child_process.execSync() 使用 git clone 命令克隆一個 Github repo 並以編程方式同步最新的更改。
開始使用
因為我們為本教程編寫的代碼是“獨立的”(意味著它不是更大的應用程序或項目的一部分),所以我們將從頭開始創建一個 Node.js 項目。如果您的計算機上尚未安裝 Node.js,請先閱讀本教程,然後再返回此處。
在計算機上安裝 Node.js 後,從計算機上的項目文件夾(例如,~/projects
),為我們的工作創建一個新文件夾:
終端
mkdir clone
接下來,cd
進入該目錄並創建一個 index.js
文件(這是我們編寫教程代碼的地方):
終端
cd clone && touch index.js
接下來,我們要安裝兩個依賴,dotenv
和 express
:
終端
npm i dotenv express
第一個將使我們能夠訪問 dotenv
幫助我們在 Node.js process.env
上設置環境變量的包 對象和第二個對象 Express,將用於啟動演示服務器。
最後一步:在 package.json
為您創建的文件,請確保添加字段 "type": "module"
作為財產。這將啟用 ESModules 支持並允許我們使用 import
如下代碼所示。
有了這些,我們就可以開始了。
從 Github 獲取個人訪問令牌
在深入研究代碼之前,我們想從 Github 獲取個人訪問令牌。這將允許我們克隆公共 和 使用我們將在下面學習的模式的私有存儲庫。
如果您還沒有 Github 帳戶,可以通過此鏈接註冊。如果你做 擁有一個帳戶,確保您已登錄,然後點擊導航右上角的頭像,然後從彈出的菜單中選擇靠近菜單底部的“設置”選項。主頁>
在下一頁的左側導航中,靠近底部,選擇“開發人員設置”選項。在下一頁的左側導航中,選擇“個人訪問令牌”選項。最後,在結果頁面中,單擊“生成新令牌”按鈕。
在下一頁的“備註”字段中,為令牌指定一個與您正在構建的應用程序相關的名稱(例如,“clone repo tutorial”或“repo cloner”)。
對於“到期”,設置您認為合適的任何值。 如果您只是為了好玩而實施本教程,明智的做法是將其設置為盡可能低的值 .
在“選擇範圍”下,選中“回購”旁邊的框以選擇所有與回購相關的範圍。這些“範圍”告訴 Github 你在使用這個令牌時可以訪問什麼。本教程只需要“repo”,但您可以隨意自定義令牌的範圍以滿足您應用的需求。
最後,在屏幕底部,點擊綠色的“生成令牌”按鈕。
注意 :注意這裡。生成令牌後,它將臨時顯示在淺綠色框中,旁邊有一個複制按鈕。 Github 不會再向您顯示此令牌 .建議您將其複制並使用“Github Personal Access Token <note>
應替換為您在上一頁的“備註”字段中鍵入的名稱。
安全存儲令牌後,我們就可以開始執行代碼了。
設置 .env 文件
之前,我們安裝了一個名為 dotenv
的包 .該軟件包旨在幫助您將環境變量加載到 process.env
Node.js 中的對象。為此,dotenv
要求您提供文件 .env
在項目的根目錄。使用我們剛剛在 Github 上生成的個人訪問令牌,我們要創建這個 .env
在我們項目的根目錄下添加文件並添加以下內容:
.env
PERSONAL_ACCESS_TOKEN="<Paste Your Token Here>"
在這個文件中,我們要添加一行 PERSONAL_ACCESS_TOKEN=""
,在雙引號中粘貼我們從 Github 獲得的令牌。接下來,我們要打開index.js
在我們項目的根目錄下添加文件並添加以下內容:
/index.js
import 'dotenv/config';
注意 :這必須在我們文件的最頂部。當這段代碼運行時,它會調用 config()
dotenv
中的函數 將定位 .env
的包 我們剛剛創建的文件並將其內容加載到 process.env
.一旦完成,我們可以期望有一個類似 process.env.PERSONAL_ACCESS_TOKEN
的值 在我們的應用程序中可用。
現在就是這樣。我們稍後會使用這個值。接下來,還是在index.js
文件,我們要為 Express.js 服務器設置骨架。
設置 Express 服務器和路由
為了觸發 repo 的克隆,現在,我們想要設置一個 Express.js 服務器,其中包含我們可以在瀏覽器中訪問的路由,指定 Github 用戶名、repo 和(可選)我們要克隆的分支名稱.
/index.js
import 'dotenv/config';
import express from "express";
const app = express();
app.get('/repos/clone/:username/:repo', (req, res) => {
// We'll handle the clone here...
});
app.listen(3000, () => {
console.log('App running at http://localhost:3000');
});
在我們的 import 'dotenv/config';
正下方 行,接下來,我們要導入 express
來自 express
我們之前安裝的軟件包。在此之下,我們想通過調用導出的 express()
創建一個 Express 服務器實例 函數並將生成的實例存儲在變量 app
中 .
app
代表我們的 Express 服務器實例。在它上面,我們要調用兩個方法:.get()
和 .listen()
. .get()
方法允許我們定義一個路由,該路由指定一個 URL 模式以及當對我們服務器的請求的 URL 匹配時要調用的處理函數 那種模式。
在這裡,我們調用 app.get()
將該 URL 模式作為字符串 /repos/clone/:username/:repo
傳遞 , 其中 :username
和 :repo
是所謂的路由參數。這些是我們 URL 中的“變量”,允許我們重用相同的 URL 模式,同時期待不同的輸入。
例如,這條路線可以作為 /repos/clone/cheatcode/joystick
訪問 或 /repos/clone/motdotla/dotenv
甚至是 /repos/clone/microsoft/vscode
.在最後一個示例中,microsoft
將被識別為 username
和 vscode
將被識別為 repo
.
在我們編寫代碼之前,在分配給 app.get()
的第二個參數的處理函數中克隆我們的 repo ,在我們文件的底部,我們要確保啟動我們的 Express.js 服務器,並為其提供一個運行端口號。為此,我們調用 app.listen()
,傳遞我們要用作第一個參數的端口號。作為第二個參數,我們傳遞一個回調函數在服務器啟動後觸發(我們添加一個 console.log()
在我們的終端中向我們發送啟動信號)。
/index.js
import 'dotenv/config';
import express from "express";
import fs from 'fs';
import cloneAndPullRepo from './cloneAndPullRepo.js';
const app = express();
app.get('/repos/clone/:username/:repo', (req, res) => {
const username = req?.params?.username;
const repo = req?.params?.repo;
const repoPath = `${username}/${repo}`;
const repoExists = fs.existsSync(`repos/${repoPath}`);
const confirmation = repoExists ? `Pulling ${repoPath}...` : `Cloning ${repoPath}...`;
cloneAndPullRepo(repoExists, username, repo, req?.query?.branch);
res.status(200).send(confirmation);
});
app.listen(3000, () => {
console.log('App running at http://localhost:3000');
});
開始我們的實際實現,我們希望將注意力集中在作為第二個參數傳遞給 app.get()
的處理函數中 .
在這裡,我們正在組織執行克隆所需的信息。從我們的路由參數(這裡是“params”)中,我們想要得到 username
和 repo
我們的 URL 的一部分。為此,我們只需訪問 req.params
Express 自動提供給我們的對象。我們期望 req.params.username
和 req.params.repo
被定義,因為我們可以看到在我們的 URL 中聲明了這些參數(任何以 :
為前綴的 我們的 URL 中的冒號被捕獲為參數)。
在這裡,我們存儲 username
和 repo
來自 req.params
在同名變量中。有了這些,接下來,我們設置repoPath
這是 username
的組合 和 repo
, 由 /
分隔 正斜杠(模仿你在 Github 上訪問的 URL)。
有了這些信息,接下來,我們檢查 repos
中是否已經存在一個文件夾 我們打算將所有 repos 存儲在我們項目的根目錄中的文件夾(這不存在,但會在我們第一次克隆 repo 時由 Git 自動創建)。
在下一行,如果它確實 存在,我們想向請求發出信號,表明我們正在拉動 回購(意思是,拉最新的變化),如果它沒有 存在,我們想發回信號,我們是第一次克隆它。我們將描述任一場景的字符串存儲在變量 confirmation
中 .
我們可以看到這個confirmation
變量通過 res
發送回原始請求 快遞給我們的物品。在這裡,我們說“將 HTTP 狀態碼設置為 200(成功),然後發送 confirmation
字符串作為響應正文。”
在此之上,我們關心的部分,我們調用了一個不存在的函數 cloneAndPullRepo()
它將接受我們剛剛定義的變量,然後克隆一個新的 repo 或為現有的 repo 拉取更改。請注意,我們傳遞了我們預定義的 repoExists
, username
, 和 repo
變量作為前三個參數,但我們在最後添加了一個。
或者,我們希望讓我們的用戶能夠為他們的 repo 拉一個特定的分支。因為這是可選 (意味著它可能存在也可能不存在),我們希望將其作為 查詢 來支持 範圍。這與路由參數的不同之處在於它不 指示路由是否匹配 一個網址。它只是作為元數據添加到 URL 的末尾(例如,/repos/clone/cheatcode/joystick?branch=development
)。
然而,就像路由參數一樣,Express 也會為我們解析這些查詢參數,並將它們存儲在 req.query
目的。到預期的cloneAndPullRepo()
函數,我們傳遞 req.query.branch
作為最後的論點。
有了所有這些,現在,讓我們跳到克隆和拉動步驟。我們想在文件 cloneAndPullRepo.js
頂部附近的預期路徑處創建一個文件 .
連接克隆和拉取函數
現在,在一個新文件中,我們想要連接一個函數,負責執行我們的 repo 的克隆或拉取。
/cloneAndPullRepo.js
import child_process from 'child_process';
export default (repoExists = false, username = '', repo = '', branch = 'master') => {
if (!repoExists) {
child_process.execSync(`git clone https://${username}:${process.env.PERSONAL_ACCESS_TOKEN}@github.com/${username}/${repo}.git repos/${username}/${repo}`);
} else {
child_process.execSync(`cd repos/${username}/${repo} && git pull origin ${branch} --rebase`);
}
}
由於代碼有限,我們在此處添加了文件的完整源代碼。讓我們一步一步來。
首先,在我們文件的底部,我們要創建一個函數的默認導出(這是我們預期在 index.js
中存在的函數 )。該函數應該考慮 repoExists
, username
我們要克隆(或拉取)的 repo 的名稱,以及 repo
的名稱 我們想要克隆,並且可能是一個 branch
.
對於每個參數,我們設置一個默認值,重要的兩個是 repoExists
默認設置為 false
和 branch
默認設置為 master
.
查看代碼——確認 child_process
的導入 從內置 Node.js child_process
開始 被動打包——如果 repoExists
是錯誤 ,我們要調用child_process.execSync()
該函數允許我們在 Node.js 中運行與我們的操作系統相關的命令(就像我們在終端窗口中一樣)。
這裡,execSync
意味著我們正在使用 同步 child_process.exec()
的版本 功能。這樣做是為了確保克隆適用於我們的示例,但是,您可能希望使用異步 .exec()
方法,這樣,當被調用時,代碼在運行時不會阻塞 Node.js。
專注於什麼 我們傳遞給 .execSync()
,我們使用 JavaScript 字符串插值傳遞一個長命令,將我們的變量嵌入到 git clone
我們要運行的命令:
`git clone https://${username}:${process.env.PERSONAL_ACCESS_TOKEN}@github.com/${username}/${repo}.git repos/${username}/${repo}`
大部分內容應該是不言自明的,但是,我們想提請注意 process.env.PERSONAL_ACCESS_TOKEN
部分。這是我們之前通過 dotenv
設置的值 包和我們的 .env
文件。在這裡,我們將它作為我們想要驗證 git clone
的密碼傳遞 請求(Github 將識別此訪問令牌,這要歸功於其前綴 ghp_
身份並將其與我們的帳戶相關聯)。
例如,假設我們訪問了 URL http://localhost:3000/repos/clone/cheatcode/joystick
在我們的瀏覽器中,我們希望上面的代碼生成這樣的字符串:
git clone https://cheatcode:[email protected]/cheatcode/joystick.git repos/cheatcode/joystick
這行現在說的是“我們要克隆 cheatcode/joystick
使用用戶名 cheatcode
的 repo 使用密碼 ghp_xxx
進入 repos/cheatcode/joystick
我們應用程序中的文件夾。”
當它運行時,Git 會注意到 repos
文件夾還不存在並創建它,以及我們的用戶名 cheatcode
的文件夾 然後在那個中 , 一個包含我們 repo
的文件夾 名稱(我們項目的代碼將被克隆的地方)。
/cloneAndPullRepo.js
import child_process from 'child_process';
export default (repoExists = false, username = '', repo = '', branch = 'master') => {
if (!repoExists) {
child_process.execSync(`git clone https://${username}:${process.env.PERSONAL_ACCESS_TOKEN}@github.com/${username}/${repo}.git repos/${username}/${repo}`);
} else {
child_process.execSync(`cd repos/${username}/${repo} && git pull origin ${branch} --rebase`);
}
}
專注於函數的第二部分,if repoExists
是 true
,我們想回退到 else
語句,再次使用 .execSync()
,但是,這次運行兩個命令:cd
將目錄“更改”到現有的 repos/username/repo
文件夾,然後是 git pull origin ${branch} --rebase
拉取指定 branch
的最新更改 (默認的 master
或作為查詢參數傳遞給我們 URL 的任何內容)。
而已。有了所有這些,現在,如果我們啟動我們的應用程序並在我們的 URL 中傳遞現有 Github 存儲庫的用戶名和存儲庫名稱(或者是公共的,或者,如果是私有的,我們可以訪問的),我們應該觸發 cloneAndPullRepo()
函數並查看下載到我們項目中的 repo。
總結
在本教程中,我們學習瞭如何使用 Node.js 克隆 Github 存儲庫。我們學習瞭如何設置 Express.js 服務器,以及可以調用函數的路由,該函數可以克隆一個新的 repo,或者提取一個現有的 repo。為了進行克隆或拉取,我們學習瞭如何使用 child_process.execSync()
功能。