diff options
author | Eric Engeström | 2015-06-09 01:37:37 +0200 |
---|---|---|
committer | Eric Engeström | 2015-06-09 01:37:37 +0200 |
commit | 8180efe6ce1b78f1325c913342810dba3fd2b7f9 (patch) | |
tree | c98327b76232528261fb358ef849d369d481da19 | |
download | aur-8180efe6ce1b78f1325c913342810dba3fd2b7f9.tar.gz |
initial commit - r4-1
-rw-r--r-- | .SRCINFO | 16 | ||||
-rw-r--r-- | PKGBUILD | 22 | ||||
-rw-r--r-- | gitbrute.go | 203 |
3 files changed, 241 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO new file mode 100644 index 000000000000..834224bffb81 --- /dev/null +++ b/.SRCINFO @@ -0,0 +1,16 @@ +pkgbase = gitbrute + pkgdesc = Brute-force a git commit hash + pkgver = 4 + pkgrel = 1 + url = https://github.com/bradfitz/gitbrute + arch = x86_64 + arch = i686 + license = unknown + makedepends = go + provides = git-brute + options = !strip + source = https://github.com/bradfitz/gitbrute/raw/e8909ca1ffc24f36be21a96567f43cde52f67491/gitbrute.go + sha256sums = 702163a9b6e70d98f09ab92fc949c26360d229a7e6f05c821434ccc1532116d1 + +pkgname = gitbrute + diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 000000000000..3ff65f905e05 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,22 @@ +# Maintainer: Eric Engestrom <aur [at] engestrom [dot] ch> + +pkgname=gitbrute +pkgver=4 +pkgrel=1 +pkgdesc="Brute-force a git commit hash" +arch=('x86_64' 'i686') +url='https://github.com/bradfitz/gitbrute' +license=('unknown') +makedepends=('go') +source=("$url/raw/e8909ca1ffc24f36be21a96567f43cde52f67491/gitbrute.go") +sha256sums=('702163a9b6e70d98f09ab92fc949c26360d229a7e6f05c821434ccc1532116d1') +options=('!strip') +provides=('git-brute') + +build() { + go build -o $pkgname +} + +package() { + install -Dm755 "$pkgname" "$pkgdir/usr/bin/$provides" +} diff --git a/gitbrute.go b/gitbrute.go new file mode 100644 index 000000000000..7206a7d8e643 --- /dev/null +++ b/gitbrute.go @@ -0,0 +1,203 @@ +/* +Copyright 2014 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// The gitbrute command brute-forces a git commit hash prefix. +package main + +import ( + "bytes" + "crypto/sha1" + "flag" + "fmt" + "log" + "os" + "os/exec" + "regexp" + "runtime" + "strconv" + "strings" + "time" +) + +var ( + prefix = flag.String("prefix", "bf", "Desired prefix") + force = flag.Bool("force", false, "Re-run, even if current hash matches prefix") + cpu = flag.Int("cpus", runtime.NumCPU(), "Number of CPUs to use. Defaults to number of processors.") +) + +var ( + start = time.Now() + startUnix = start.Unix() +) + +func main() { + flag.Parse() + runtime.GOMAXPROCS(*cpu) + if _, err := strconv.ParseInt(*prefix, 16, 64); err != nil { + log.Fatalf("Prefix %q isn't hex.", *prefix) + } + + hash := curHash() + if strings.HasPrefix(hash, *prefix) && !*force { + return + } + + obj, err := exec.Command("git", "cat-file", "-p", hash).Output() + if err != nil { + log.Fatal(err) + } + i := bytes.Index(obj, []byte("\n\n")) + if i < 0 { + log.Fatalf("No \\n\\n found in %q", obj) + } + msg := obj[i+2:] + + possibilities := make(chan try, 512) + go explore(possibilities) + winner := make(chan solution) + for i := 0; i < *cpu; i++ { + go bruteForce(obj, winner, possibilities) + } + + w := <-winner + cmd := exec.Command("git", "commit", "--amend", "--date="+w.author.String(), "--file=-") + cmd.Env = append([]string{"GIT_COMMITTER_DATE=" + w.committer.String()}, os.Environ()...) + cmd.Stdout = os.Stdout + cmd.Stdin = bytes.NewReader(msg) + if err := cmd.Run(); err != nil { + log.Fatalf("amend: %v", err) + } +} + +type solution struct { + author, committer date +} + +var ( + authorDateRx = regexp.MustCompile(`(?m)^author.+> (.+)`) + commiterDateRx = regexp.MustCompile(`(?m)^committer.+> (.+)`) +) + +func bruteForce(obj []byte, winner chan<- solution, possibilities <-chan try) { + // blob is the blob to mutate in-place repatedly while testing + // whether we have a match. + blob := []byte(fmt.Sprintf("commit %d\x00%s", len(obj), obj)) + authorDate, adatei := getDate(blob, authorDateRx) + commitDate, cdatei := getDate(blob, commiterDateRx) + + s1 := sha1.New() + wantHexPrefix := []byte(*prefix) + hexBuf := make([]byte, 0, sha1.Size*2) + + for t := range possibilities { + ad := date{startUnix - int64(t.authorBehind), authorDate.tz} + cd := date{startUnix - int64(t.commitBehind), commitDate.tz} + strconv.AppendInt(blob[:adatei], ad.n, 10) + strconv.AppendInt(blob[:cdatei], cd.n, 10) + s1.Reset() + s1.Write(blob) + if !bytes.HasPrefix(hexInPlace(s1.Sum(hexBuf[:0])), wantHexPrefix) { + continue + } + winner <- solution{ad, cd} + return // at least yield one goroutine's CPU for git commit to run. + } +} + +// try is a pair of seconds behind now to brute force, looking for a +// matching commit. +type try struct { + commitBehind int + authorBehind int +} + +// explore yields the sequence: +// (0, 0) +// +// (0, 1) +// (1, 0) +// (1, 1) +// +// (0, 2) +// (1, 2) +// (2, 0) +// (2, 1) +// (2, 2) +// +// ... +func explore(c chan<- try) { + for max := 0; ; max++ { + for i := 0; i <= max-1; i++ { + c <- try{i, max} + } + for j := 0; j <= max; j++ { + c <- try{max, j} + } + } +} + +// date is a git date. +type date struct { + n int64 // unix seconds + tz string +} + +func (d date) String() string { return fmt.Sprintf("%d %s", d.n, d.tz) } + +// getDate parses out a date from a git header (or blob with a header +// following the size and null byte). It returns the date and index +// that the unix seconds begins at within h. +func getDate(h []byte, rx *regexp.Regexp) (d date, idx int) { + m := rx.FindSubmatchIndex(h) + if m == nil { + log.Fatalf("Failed to match %s in %q", rx, h) + } + v := string(h[m[2]:m[3]]) + space := strings.Index(v, " ") + if space < 0 { + log.Fatalf("unexpected date %q", v) + } + n, err := strconv.ParseInt(v[:space], 10, 64) + if err != nil { + log.Fatalf("unexpected date %q", v) + } + return date{n, v[space+1:]}, m[2] +} + +func curHash() string { + all, err := exec.Command("git", "rev-parse", "HEAD").Output() + if err != nil { + log.Fatal(err) + } + h := string(all) + if i := strings.Index(h, "\n"); i > 0 { + h = h[:i] + } + return h +} + +// hexInPlace takes a slice of binary data and returns the same slice with double +// its length, hex-ified in-place. +func hexInPlace(v []byte) []byte { + const hex = "0123456789abcdef" + h := v[:len(v)*2] + for i := len(v) - 1; i >= 0; i-- { + b := v[i] + h[i*2+0] = hex[b>>4] + h[i*2+1] = hex[b&0xf] + } + return h +} |