GitなしでGit SHA1をファイルに割り当てる方法は?


138

私が理解しているように、GitがSHA1ハッシュをファイルに割り当てるとき、このSHA1はその内容に基づいてファイルに固有です。

その結果、ファイルがあるリポジトリから別のリポジトリに移動した場合、ファイルのSHA1は、その内容が変更されていないので同じままです。

GitはSHA1ダイジェストをどのように計算しますか?それは完全な非圧縮ファイルの内容でそれをしますか?

Gitの外部でSHA1を割り当てることをエミュレートしたいと思います。




回答:


255

これは、Gitがファイル(またはGit用語では「blob」)のSHA1を計算する方法です。

sha1("blob " + filesize + "\0" + data)

したがって、Gitをインストールしなくても、自分で簡単に計算できます。「\ 0」はNULL文字であり、2文字の文字列ではないことに注意してください。

たとえば、空のファイルのハッシュ:

sha1("blob 0\0") = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"

$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

もう一つの例:

sha1("blob 7\0foobar\n") = "323fae03f4606ea9991df8befbb2fca795e648fa"

$ echo "foobar" > foo.txt
$ git hash-object foo.txt 
323fae03f4606ea9991df8befbb2fca795e648fa

Pythonの実装は次のとおりです。

from hashlib import sha1
def githash(data):
    s = sha1()
    s.update("blob %u\0" % len(data))
    s.update(data)
    return s.hexdigest()

この答えはPython 2を想定していますか?Python 3でこれを試すとTypeError: Unicode-objects must be encoded before hashing、最初のs.update()行で例外が発生します。
Mark Booth 2013年

3
python 3を使用すると、データをエンコードする必要がありますs.update(("blob %u\0" % filesize).encode('utf-8'))避けるためにTypeError
マークブース2013年

utf-8としてエンコードすることもできますが、最初にバイト文字列から構築する方がおそらく良いでしょう(UTF-8エンコードは、ASCII以外のUnicode文字がないため機能します)。
torek

言及する価値があるもう1つのことは、git hash-objectもデータのコンテンツで「\ r \ n」を「\ n」に置き換えるように見えることです。「\ r」を完全に取り除く可能性があるので、チェックしませんでした。
user420667

1
私はファイルツリーハッシュジェネレーターのPython 2 + 3(両方1つ)の実装をここに配置しました:github.com/chris3torek/scripts/blob/master/githash.py(ツリーのハッシュはディレクトリツリーを読み取ります)。
torek

17

ちょっとした利点:シェル

echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum

1
私はecho -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sumの出力と比較していますがgit hash-object path-to-file、結果は異なります。ただし、echo -e ...末尾があることを除いて、正しい結果が生成されます- (末尾の文字git hash-objectは生成されません)。これは私が心配すべきことですか?
FrustratedWithFormsDesigner 2015

2
@FrustratedWithFormsDesigner:末尾-sha1sum、ファイルからではなく標準入力からハッシュを計算する場合に使用されます。心配することは何もありません。奇妙なことですが、-n通常はエコーによって追加される改行を抑制します。あなたのファイルには、CONTENTS変数に追加するのを忘れた空の最後の行がありますか?
knittl 2015

はい、あなたは正しいです。そして、私はsha1sumの出力はハッシュだけであるべきだと思っていましたが、sedなどでそれを削除することは難しくありません。
FrustratedWithFormsDesigner 2015

@FrustratedWithFormsDesigner:cat file | sha1sum代わりにsha1sum file(より多くのプロセスとパイピング)を使用すると、同じ出力が得られます
knittl

8

gitがインストールされていない場合は、bashシェル関数を作成して非常に簡単に計算できます。

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }

1
少し短く:(stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1
sschuberth

4

git-hash-objectの manページをご覧ください。これを使用して、特定のファイルのgitハッシュを計算できます。私が考えているgitのフィードより多くのハッシュアルゴリズムにファイルの内容だけよりも、私は確かに知っていない、そしてそれは、余分なデータにフィードしなければ、私はそれが何であるかを知りません。


2
/// Calculates the SHA1 for a given string
let calcSHA1 (text:string) =
    text 
      |> System.Text.Encoding.ASCII.GetBytes
      |> (new System.Security.Cryptography.SHA1CryptoServiceProvider()).ComputeHash
      |> Array.fold (fun acc e -> 
           let t = System.Convert.ToString(e, 16)
           if t.Length = 1 then acc + "0" + t else acc + t) 
           ""
/// Calculates the SHA1 like git
let calcGitSHA1 (text:string) =
    let s = text.Replace("\r\n","\n")
    sprintf "blob %d%c%s" (s.Length) (char 0) s
      |> calcSHA1

これはF#のソリューションです。


ウムラウトにまだ問題があります。git hash-objectがウムラウトを処理する方法についてのアイデアはありますか?
forki23

これはblobをバイトストリームとして処理する必要があります。つまり、üの長さはおそらく2(ユニコード)であり、F♯のLengthプロパティは長さ1を返します(1つの表示文字のみであるため)
knittl

ただし、System.Text.Encoding.ASCII.GetBytes( "ü")は、1つの要素を持つバイト配列を返します。
forki23

文字列の長さとしてUTF8と2を使用すると、バイト配列が得られます:[98; 108; 111; 98; 32; 50; 0; 195; 188]したがって、SHA1は99fe40df261f7d4afd1391fe2739b2c7466fe968です。これもgit SHA1ではありません。
forki23

1
文字列にダイジェストを適用しないでください。代わりに、明示的なエンコーディングを使用して文字列をバイトに変換することで取得できるバイト文字列(バイト配列)にそれらを適用する必要があります。
ドルメン2011

2

完全なPython3実装:

import os
from hashlib import sha1

def hashfile(filepath):
    filesize_bytes = os.path.getsize(filepath)

    s = sha1()
    s.update(b"blob %u\0" % filesize_bytes)

    with open(filepath, 'rb') as f:
        s.update(f.read())

    return s.hexdigest() 

2
あなたが本当に欲しいのはASCIIエンコーディングです。それはASCIIと互換性があり、「ブロブが\ 0 x」のコードのみで文字が含まれているため、UTF8は、ここでしか動作します<= 127
フェルディナント・バイエル

1

Perlの場合:

#!/usr/bin/env perl
use Digest::SHA1;

my $content = do { local $/ = undef; <> };
print Digest::SHA1->new->add('blob '.length($content)."\0".$content)->hexdigest(), "\n";

シェルコマンドとして:

perl -MDigest::SHA1 -E '$/=undef;$_=<>;say Digest::SHA1->new->add("blob ".length()."\0".$_)->hexdigest' < file


1

Rubyを使用すると、次のようなことができます。

require 'digest/sha1'

def git_hash(file)
  data = File.read(file)
  size = data.bytesize.to_s
  Digest::SHA1.hexdigest('blob ' + size + "\0" + data)
end

1

と同じ出力を生成する小さなBashスクリプトgit hash-object

#!/bin/sh
( 
    echo -en 'blob '"$(stat -c%s "$1")"'\0';
    cat "$1" 
) | sha1sum | cut -d\  -f 1

0

JavaScriptで

const crypto = require('crypto')
const bytes = require('utf8-bytes')

function sha1(data) {
    const shasum = crypto.createHash('sha1')
    shasum.update(data)
    return shasum.digest('hex')
}

function shaGit(data) {
    const total_bytes = bytes(data).length
    return sha1(`blob ${total_bytes}\0${data}`)
}

-4

興味深いことに、Gitは明らかに、ハッシュされる前にデータの最後に改行文字を追加します。「Hello World!」のみを含むファイル 980a0d5 ...のblobハッシュを取得します。これは次のものと同じです。

$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'

4
その改行は、ではなくテキストエディタによって追加されますgit hash-object。やっていることを注意はecho "Hello World!" | git hash-object --stdin与え980a0d5...使用は一方で、echo -nのハッシュ与えc57eff5...代わりに。
bdesham 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.