<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                在本節中,我們應用前面學到的知識建立這樣一個Git 工作流程:檢查提交信息的格式,只接受純fast-forward內容的推送,并且指定用戶只能修改項目中的特定子目錄。我們將寫一個客戶端腳本來提示開發人員他們推送的內容是否會被拒絕,以及一個服務端腳本來實際執行這些策略。 這些腳本使用 Ruby 寫成,一半由于它是作者傾向的腳本語言,另外作者覺得它是最接近偽代碼的腳本語言;因而即便你不使用 Ruby 也能大致看懂。不過任何其他語言也一樣適用。所有 Git 自帶的樣例腳本都是用 Perl 或 Bash 寫的。所以從這些腳本中能找到相當多的這兩種語言的掛鉤樣例。 ## 服務端掛鉤 所有服務端的工作都在hooks(掛鉤)目錄的 update(更新)腳本中制定。update 腳本為每一個得到推送的分支運行一次;它接受推送目標的索引,該分支原來指向的位置,以及被推送的新內容。如果推送是通過 SSH 進行的,還可以獲取發出此次操作的用戶。如果設定所有操作都通過公匙授權的單一帳號(比如"git")進行,就有必要通過一個 shell 包裝依據公匙來判斷用戶的身份,并且設定環境變量來表示該用戶的身份。下面假設嘗試連接的用戶儲存在 $USER 環境變量里,我們的 update 腳本首先搜集一切需要的信息: ~~~ !/usr/bin/env ruby refname = ARGV[0] oldrev = ARGV[1] newrev = ARGV[2] user = ENV['USER'] puts "Enforcing Policies... \n(#{refname}) (#{oldrev[0,6]}) (#{newrev[0,6]})" ~~~ ### 指定特殊的提交信息格式 我們的第一項任務是指定每一條提交信息都必須遵循某種特殊的格式。作為演示,假定每一條信息必須包含一條形似 "ref: 1234" 這樣的字符串,因為我們需要把每一次提交和項目的問題追蹤系統。我們要逐一檢查每一條推送上來的提交內容,看看提交信息是否包含這么一個字符串,然后,如果該提交里不包含這個字符串,以非零返回值退出從而拒絕此次推送。 把 $newrev 和 $oldrev 變量的值傳給一個叫做 `git rev-list `的 `Git plumbing `命令可以獲取所有提交內容的 SHA-1 值列表。`git rev-list `基本類似 `git log `命令,但它默認只輸出 SHA-1 值而已,沒有其他信息。所以要獲取由 SHA 值表示的從一次提交到另一次提交之間的所有 SHA 值,可以運行: ~~~ $ git rev-list 538c33..d14fc7 d14fc7c847ab946ec39590d87783c69b031bdfb7 9f585da4401b0a3999e84113824d15245c13f0be 234071a1be950e2a8d078e6141f5cd20c1e61ad3 dfa04c9ef3d5197182f13fb5b9b1fb7717d2222a 17716ec0f1ff5c77eff40b7fe912f9f6cfd0e475 ~~~ 截取這些輸出內容,循環遍歷其中每一個 SHA 值,找出與之對應的提交信息,然后用正則表達式來測試該信息包含的格式話的內容。 下面要搞定如何從所有的提交內容中提取出提交信息。使用另一個叫做 git cat-file 的 Git plumbing 工具可以獲得原始的提交數據。我們將在第九章了解到這些 plumbing 工具的細節;現在暫時先看一下這條命令的輸出: ~~~ $ git cat-file commit ca82a6 tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 author Scott Chacon <schacon@gmail.com> 1205815931 -0700 committer Scott Chacon <schacon@gmail.com> 1240030591 -0700 changed the version number ~~~ 通過 SHA-1 值獲得提交內容中的提交信息的一個簡單辦法是找到提交的第一行,然后取從它往后的所有內容。可以使用 Unix 系統的 sed 命令來實現該效果: ~~~ $ git cat-file commit ca82a6 | sed '1,/^$/d' changed the version number ~~~ 這條咒語從每一個待提交內容里提取提交信息,并且會在提取信息不符合要求的情況下退出。為了退出腳本和拒絕此次推送,返回一個非零值。整個腳本大致如下: `$regex = /\[ref: (\d+)\]/` ### 指定提交信息格式 ~~~ def check_message_format missed_revs = `git rev-list #{$oldrev}..#{$newrev}`.split("\n") missed_revs.each do |rev| message = `git cat-file commit #{rev} | sed '1,/^$/d'` if !$regex.match(message) puts "[POLICY] Your message is not formatted correctly" exit 1 end end end check_message_format ~~~ 把這一段放在 update 腳本里,所有包含不符合指定規則的提交都會遭到拒絕。 ### 實現基于用戶的訪問權限控制列表(ACL)系統 假設你需要添加一個使用訪問權限控制列表的機制來指定哪些用戶對項目的哪些部分有推送權限。某些用戶具有全部的訪問權,其他人只對某些子目錄或者特定的文件具有推送權限。要搞定這一點,所有的規則將被寫入一個位于服務器的原始 Git 倉庫的 acl 文件。我們讓 update 掛鉤檢閱這些規則,審視推送的提交內容中需要修改的所有文件,然后決定執行推送的用戶是否對所有這些文件都有權限。 我們首先要創建這個列表。這里使用的格式和 CVS 的 ACL 機制十分類似:它由若干行構成,第一項內容是 avail 或者 unavail,接著是逗號分隔的規則生效用戶列表,最后一項是規則生效的目錄(空白表示開放訪問)。這些項目由 | 字符隔開。 下例中,我們指定幾個管理員,幾個對 doc 目錄具有權限的文檔作者,以及一個對 lib 和 tests 目錄具有權限的開發人員,相應的 ACL 文件如下: ~~~ avail|nickh,pjhyett,defunkt,tpw avail|usinclair,cdickens,ebronte|doc avail|schacon|lib avail|schacon|tests ~~~ 首先把這些數據讀入你編寫的數據結構。本例中,為保持簡潔,我們暫時只實現 avail 的規則(譯注:也就是省略了 unavail 部分)。下面這個方法生成一個關聯數組,它的主鍵是用戶名,值是一個該用戶有寫權限的所有目錄組成的數組: ~~~ def get_acl_access_data(acl_file) # read in ACL data acl_file = File.read(acl_file).split("\n").reject { |line| line == '' } access = {} acl_file.each do |line| avail, users, path = line.split('|') next unless avail == 'avail' users.split(',').each do |user| access[user] ||= [] access[user] << path end end access end ~~~ 針對之前給出的 ACL 規則文件,這個 get_acl_access_data 方法返回的數據結構如下: ~~~ {"defunkt"=>[nil], "tpw"=>[nil], "nickh"=>[nil], "pjhyett"=>[nil], "schacon"=>["lib", "tests"], "cdickens"=>["doc"], "usinclair"=>["doc"], "ebronte"=>["doc"]} ~~~ 搞定了用戶權限的數據,下面需要找出哪些位置將要被提交的內容修改,從而確保試圖推送的用戶對這些位置有全部的權限。 使用 git log 的 --name-only 選項(在第二章里簡單的提過)我們可以輕而易舉的找出一次提交里修改的文件: ~~~ $ git log -1 --name-only --pretty=format:'' 9f585d README lib/test.rb ~~~ 使用 get_acl_access_data 返回的 ACL 結構來一一核對每一次提交修改的文件列表,就能找出該用戶是否有權限推送所有的提交內容: ~~~ # 僅允許特定用戶修改項目中的特定子目錄 def check_directory_perms access = get_acl_access_data('acl') # 檢查是否有人在向他沒有權限的地方推送內容 new_commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n") new_commits.each do |rev| files_modified = `git log -1 --name-only --pretty=format:'' #{rev}`.split("\n") files_modified.each do |path| next if path.size == 0 has_file_access = false access[$user].each do |access_path| if !access_path || # 用戶擁有完全訪問權限 (path.index(access_path) == 0) # 或者對此位置有訪問權限 has_file_access = true end end if !has_file_access puts "[POLICY] You do not have access to push to #{path}" exit 1 end end end end check_directory_perms ~~~ 以上的大部分內容應該都比較容易理解。通過 git rev-list 獲取推送到服務器內容的提交列表。然后,針對其中每一項,找出它試圖修改的文件然后確保執行推送的用戶對這些文件具有權限。一個不太容易理解的 Ruby 技巧是` path.index(access_path) ==0` 這句,它的返回真值如果路徑以 `access_path `開頭——這是為了確保 `access_path `并不是只在允許的路徑之一,而是所有準許全選的目錄都在該目錄之下。 現在你的用戶沒法推送帶有不正確的提交信息的內容,也不能在準許他們訪問范圍之外的位置做出修改。 ### 只允許 Fast-Forward 類型的推送 剩下的最后一項任務是指定只接受 `fast-forward` 的推送。在 Git 1.6 或者更新版本里,只需要設定 `receive.denyDeletes` 和 `receive.denyNonFastForwards `選項就可以了。但是通過掛鉤的實現可以在舊版本的 Git 上工作,并且通過一定的修改它它可以做到只針對某些用戶執行,或者更多以后可能用的到的規則。 檢查這一項的邏輯是看看提交里是否包含從舊版本里能找到但在新版本里卻找不到的內容。如果沒有,那這是一次純 `fast-forward` 的推送;如果有,那我們拒絕此次推送: ~~~ # 只允許純 fast-forward 推送 def check_fast_forward missed_refs = `git rev-list #{$newrev}..#{$oldrev}` missed_ref_count = missed_refs.split("\n").size if missed_ref_count > 0 puts "[POLICY] Cannot push a non fast-forward reference" exit 1 end end check_fast_forward ~~~ 一切都設定好了。如果現在運行` chmod u+x .git/hooks/update `—— 修改包含以上內容文件的權限,然后嘗試推送一個包含非 `fast-forward` 類型的索引,會得到一下提示: ~~~ $ git push -f origin master Counting objects: 5, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 323 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. Enforcing Policies... (refs/heads/master) (8338c5) (c5b616) [POLICY] Cannot push a non fast-forward reference error: hooks/update exited with error code 1 error: hook declined to update refs/heads/master To git@gitserver:project.git ! [remote rejected] master -> master (hook declined) error: failed to push some refs to 'git@gitserver:project.git' ~~~ 這里有幾個有趣的信息。首先,我們可以看到掛鉤運行的起點: ~~~ Enforcing Policies... (refs/heads/master) (8338c5) (c5b616) ~~~ 注意這是從 update 腳本開頭輸出到標準你輸出的。所有從腳本輸出的提示都會發送到客戶端,這點很重要。 下一個值得注意的部分是錯誤信息。 ~~~ [POLICY] Cannot push a non fast-forward reference error: hooks/update exited with error code 1 error: hook declined to update refs/heads/master ~~~ 第一行是我們的腳本輸出的,在往下是 Git 在告訴我們 update 腳本退出時返回了非零值因而推送遭到了拒絕。最后一點: ~~~ To git@gitserver:project.git ! [remote rejected] master -> master (hook declined) error: failed to push some refs to 'git@gitserver:project.git' ~~~ 我們將為每一個被掛鉤拒之門外的索引受到一條遠程信息,解釋它被拒絕是因為一個掛鉤的原因。 而且,如果那個 ref 字符串沒有包含在任何的提交里,我們將看到前面腳本里輸出的錯誤信息: `[POLICY] Your message is not formatted correctly` 又或者某人想修改一個自己不具備權限的文件然后推送了一個包含它的提交,他將看到類似的提示。比如,一個文檔作者嘗試推送一個修改到 lib 目錄的提交,他會看到 `[POLICY] You do not have access to push to lib/test.rb` 全在這了。從這里開始,只要 update 腳本存在并且可執行,我們的倉庫永遠都不會遭到回轉或者包含不符合要求信息的提交內容,并且用戶都被鎖在了沙箱里面。 ## 客戶端掛鉤 這種手段的缺點在于用戶推送內容遭到拒絕后幾乎無法避免的抱怨。辛辛苦苦寫成的代碼在最后時刻慘遭拒絕是十分悲劇且具有迷惑性的;更可憐的是他們不得不修改提交歷史來解決問題,這怎么也算不上王道。 逃離這種兩難境地的法寶是給用戶一些客戶端的掛鉤,在他們作出可能悲劇的事情的時候給以警告。然后呢,用戶們就能在提交--問題變得更難修正之前解除隱患。由于掛鉤本身不跟隨克隆的項目副本分發,所以必須通過其他途徑把這些掛鉤分發到用戶的 .git/hooks 目錄并設為可執行文件。雖然可以在相同或單獨的項目內 容里加入并分發它們,全自動的解決方案是不存在的。 首先,你應該在每次提交前核查你的提交注釋信息,這樣你才能確保服務器不會因為不合條件的提交注釋信息而拒絕你的更改。為了達到這個目的,你可以增加'commit-msg'掛鉤。如果你使用該掛鉤來閱讀作為第一個參數傳遞給git的提交注釋信息,并且與規定的模式作對比,你就可以使git在提交注釋信息不符合條件的情況下,拒絕執行提交。 ~~~ #!/usr/bin/env ruby message_file = ARGV[0] message = File.read(message_file) $regex = /\[ref: (\d+)\]/ if !$regex.match(message) puts "[POLICY] Your message is not formatted correctly" exit 1 ~~~ end 如果這個腳本放在這個位置 (`.git/hooks/commit-msg`) 并且是可執行的, 并且你的提交注釋信息不是符合要求的,你會看到: ~~~ $ git commit -am 'test' [POLICY] Your message is not formatted correctly ~~~ 在這個實例中,提交沒有成功。然而如果你的提交注釋信息是符合要求的,git會允許你提交: ~~~ $ git commit -am 'test [ref: 132]' [master e05c914] test [ref: 132] 1 files changed, 1 insertions(+), 0 deletions(-) ~~~ 接下來我們要保證沒有修改到 ACL 允許范圍之外的文件。加入你的 .git 目錄里有前面使用過的 ACL 文件,那么以下的 pre-commit 腳本將把里面的規定執行起來: ~~~ #!/usr/bin/env ruby $user = ENV['USER'] # [ insert acl_access_data method from above ] # 只允許特定用戶修改項目重特定子目錄的內容 def check_directory_perms access = get_acl_access_data('.git/acl') files_modified = `git diff-index --cached --name-only HEAD`.split("\n") files_modified.each do |path| next if path.size == 0 has_file_access = false access[$user].each do |access_path| if !access_path || (path.index(access_path) == 0) has_file_access = true end if !has_file_access puts "[POLICY] You do not have access to push to #{path}" exit 1 end end end check_directory_perms ~~~ 這和服務端的腳本幾乎一樣,除了兩個重要區別。第一,ACL 文件的位置不同,因為這個腳本在當前工作目錄運行,而非 Git 目錄。ACL 文件的目錄必須從 `access = get_acl_access_data('acl')` 修改成: `access = get_acl_access_data('.git/acl')` 另一個重要區別是獲取被修改文件列表的方式。在服務端的時候使用了查看提交紀錄的方式,可是目前的提交都還沒被記錄下來呢,所以這個列表只能從暫存區域獲取。和原來的 ~~~ files_modified = `git log -1 --name-only --pretty=format:'' #{ref}` ~~~ 不同,現在要用 ~~~ files_modified = `git diff-index --cached --name-only HEAD` ~~~ 不同的就只有這兩點——除此之外,該腳本完全相同。一個小陷阱在于它假設在本地運行的賬戶和推送到遠程服務端的相同。如果這二者不一樣,則需要手動設置一下 $user 變量。 最后一項任務是檢查確認推送內容中不包含非` fast-forward` 類型的索引,不過這個需求比較少見。要找出一個非 fast-forward 類型的索引,要么衍合超過某個已經推送過的提交,要么從本地不同分支推送到遠程相同的分支上。 既然服務器將給出無法推送非 fast-forward 內容的提示,而且上面的掛鉤也能阻止強制的推送,唯一剩下的潛在問題就是衍合一次已經推送過的提交內容。 下面是一個檢查這個問題的 pre-rabase 腳本的例子。它獲取一個所有即將重寫的提交內容的列表,然后檢查它們是否在遠程的索引里已經存在。一旦發現某個提交可以從遠程索引里衍變過來,它就放棄衍合操作: ~~~ !/usr/bin/env ruby base_branch = ARGV[0] if ARGV[1] topic_branch = ARGV[1] else topic_branch = "HEAD" end target_shas = `git rev-list #{base_branch}..#{topic_branch}`.split("\n") remote_refs = `git branch -r`.split("\n").map { |r| r.strip } target_shas.each do |sha| remote_refs.each do |remote_ref| shas_pushed = `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}` if shas_pushed.split("\n").include?(sha) puts "[POLICY] Commit #{sha} has already been pushed to #{remote_ref}" exit 1 end end end ~~~ 這個腳本利用了一個第六章"修訂版本選擇"一節中不曾提到的語法。通過這一句可以獲得一個所有已經完成推送的提交的列表: `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}` `SHA^@ `語法解析該次提交的所有祖先。這里我們從檢查遠程最后一次提交能夠衍變獲得但從所有我們嘗試推送的提交的 SHA 值祖先無法衍變獲得的提交內容——也就是 `fast-forward` 的內容。 這個解決方案的硬傷在于它有可能很慢而且常常沒有必要——只要不用 `-f `來強制推送,服務器會自動給出警告并且拒絕推送內容。然而,這是個不錯的練習而且理論上能幫助用戶避免一次將來不得不折回來修改的衍合操作。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看