From: Jeff King <peff@peff.net>
To: "Ævar Arnfjörð Bjarmason" <avarab@gmail.com>
Cc: Git Mailing List <git@vger.kernel.org>
Subject: Re: [IGNORETHIS/PATCH] Choosing the sha1 prefix of your commits
Date: Mon, 24 Oct 2011 13:47:38 -0700 [thread overview]
Message-ID: <20111024204737.GA25574@sigill.intra.peff.net> (raw)
In-Reply-To: <20111020025149.GA31549@sigill.intra.peff.net>
On Wed, Oct 19, 2011 at 10:51:49PM -0400, Jeff King wrote:
> I couldn't resist:
>
> $ git commit -q -m foo --collide=deadbeef &&
> git rev-list -1 HEAD
> deadbeefdbd6e62a2185606a4fad653e22509b56
Here's a followup with a few cleanups:
1. It will actually finish and report an error if we fail to find a
commit after ULONG_MAX tries. On 64-bit machines this is unlikely,
but it might be possible on a 32-bit machine if you have a lot of
patience.
2. It uses git_SHA* now, so it will build properly against BLK_SHA1 if
NO_OPENSSL is defined.
3. It will split the the collision-finding work across online_cpus()
threads.
Somebody else suggested farming the work out to a GPU. I'll leave that
as an exercise to the reader.
-Peff
---
builtin/commit.c | 56 ++++++++++++++++++-
commit.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
commit.h | 4 +
3 files changed, 223 insertions(+), 2 deletions(-)
diff --git a/builtin/commit.c b/builtin/commit.c
index c46f2d1..734a7ab 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -105,6 +105,10 @@
static const char *only_include_assumed;
static struct strbuf message;
+static int collide;
+static unsigned char collide_sha1[20];
+static unsigned char collide_mask[20];
+
static int null_termination;
static enum {
STATUS_FORMAT_LONG,
@@ -125,6 +129,52 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
return 0;
}
+static int parse_partial_sha1(const char *s, unsigned char sha1[20])
+{
+ unsigned int i;
+
+ hashclr(sha1);
+
+ for (i = 0; i < 40 && s[i]; i++) {
+ unsigned int v = hexval(s[i]);
+ if (v & ~0xf)
+ break;
+ if (!(i & 1))
+ v <<= 4;
+ sha1[i/2] |= v;
+ }
+ return i;
+}
+
+static void fill_sha1_mask(int n, unsigned char mask[20]) {
+ int i;
+
+ hashclr(mask);
+ for (i = 0; i < n/2; i++)
+ mask[i] = 0xff;
+ if (n & 1)
+ mask[i] = 0xf0;
+}
+
+static int opt_parse_collide(const struct option *opt, const char *arg,
+ int unset)
+{
+ if (unset)
+ collide = 0;
+ else {
+ int n = parse_partial_sha1(arg, collide_sha1);
+ if (!arg[n])
+ fill_sha1_mask(n, collide_mask);
+ else if (arg[n] == '/')
+ parse_partial_sha1(arg + n + 1, collide_mask);
+ else
+ die("invalid --collide sha1: %s", arg);
+ collide = 1;
+ }
+ return 0;
+}
+
+
static struct option builtin_commit_options[] = {
OPT__QUIET(&quiet, "suppress summary after successful commit"),
OPT__VERBOSE(&verbose, "show diff in commit message template"),
@@ -144,6 +194,7 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+ OPT_CALLBACK(0, "collide", NULL, "sha1[/mask]", "choose commit sha1 like <sha1>", opt_parse_collide),
/* end commit message options */
OPT_GROUP("Commit contents options"),
@@ -1483,8 +1534,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
exit(1);
}
- if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
- author_ident.buf)) {
+ if (commit_tree_collide(sb.buf, active_cache_tree->sha1, parents, sha1,
+ author_ident.buf,
+ collide ? collide_sha1 : NULL, collide_mask)) {
rollback_index_files();
die(_("failed to write commit object"));
}
diff --git a/commit.c b/commit.c
index 73b7e00..7beea9b 100644
--- a/commit.c
+++ b/commit.c
@@ -6,6 +6,7 @@
#include "diff.h"
#include "revision.h"
#include "notes.h"
+#include "thread-utils.h"
int save_commit_buffer = 1;
@@ -840,6 +841,158 @@ struct commit_list *reduce_heads(struct commit_list *heads)
return result;
}
+static inline int sha1_match_mask(const unsigned char *sha1,
+ const unsigned char *want,
+ const unsigned char *mask)
+{
+ int i;
+ for (i = 0; i < 20; i++)
+ if ((want[i] & mask[i]) != (sha1[i] & mask[i]))
+ return 0;
+ return 1;
+}
+
+static unsigned long find_collision(const unsigned char *want,
+ const unsigned char *mask,
+ const git_SHA_CTX *base,
+ unsigned long start,
+ unsigned long end)
+{
+ unsigned long lulz;
+
+ for (lulz = start; lulz < end; lulz++) {
+ git_SHA_CTX guess;
+ unsigned char sha1[20];
+
+ memcpy(&guess, base, sizeof(guess));
+ git_SHA1_Update(&guess, &lulz, sizeof(lulz));
+ git_SHA1_Final(sha1, &guess);
+
+ if (sha1_match_mask(sha1, want, mask))
+ return lulz;
+ }
+ return end;
+}
+
+#ifndef NO_PTHREADS
+struct collision_thread_data {
+ const unsigned char *want;
+ const unsigned char *mask;
+ const git_SHA_CTX *base;
+ unsigned long start;
+ unsigned long end;
+
+ pthread_mutex_t *mutex;
+ pthread_cond_t *cond;
+ int *threads_alive;
+ unsigned long *answer;
+};
+
+static void *collision_thread(void *vdata)
+{
+ struct collision_thread_data *d = vdata;
+ unsigned long r;
+
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+
+ r = find_collision(d->want, d->mask, d->base,
+ d->start, d->end);
+
+ pthread_mutex_lock(d->mutex);
+
+ (*d->threads_alive)--;
+ if (r != d->end) {
+ *d->answer = r;
+ pthread_cond_signal(d->cond);
+ }
+ else {
+ /* If we failed to find it, and we're the last thread,
+ * wake up the parent so it can report failure. */
+ if (!*d->threads_alive)
+ pthread_cond_signal(d->cond);
+ }
+
+ pthread_mutex_unlock(d->mutex);
+ return NULL;
+}
+
+static unsigned long find_collision_threaded(int nthreads,
+ const unsigned char *want,
+ const unsigned char *mask,
+ const git_SHA_CTX *base)
+{
+ int i;
+ pthread_t *threads;
+ struct collision_thread_data *data;
+ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+ pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+ int threads_alive = nthreads;
+ unsigned long ret = ULONG_MAX;
+
+ threads = xmalloc(nthreads * sizeof(*threads));
+ data = xmalloc(nthreads * sizeof(*data));
+
+ pthread_mutex_lock(&mutex);
+
+ for (i = 0; i < nthreads; i++) {
+ data[i].want = want;
+ data[i].mask = mask;
+ data[i].base = base;
+ data[i].start = i * (ULONG_MAX / nthreads);
+ data[i].end = (i+1) * (ULONG_MAX / nthreads);
+ data[i].mutex = &mutex;
+ data[i].cond = &cond;
+ data[i].answer = &ret;
+ data[i].threads_alive = &threads_alive;
+ pthread_create(&threads[i], NULL, collision_thread, &data[i]);
+ }
+
+ pthread_cond_wait(&cond, &mutex);
+
+ for (i = 0; i < nthreads; i++) {
+ pthread_cancel(threads[i]);
+ pthread_join(threads[i], NULL);
+ }
+
+ free(threads);
+ free(data);
+ return ret;
+}
+#endif /* NO_PTHREADS */
+
+
+static void collide_commit(struct strbuf *data,
+ const unsigned char *want,
+ const unsigned char *mask)
+{
+ static const char terminator[] = { 0 };
+ char header[32];
+ int header_len;
+ unsigned long lulz;
+ git_SHA_CTX base;
+
+ header_len = snprintf(header, sizeof(header),
+ "commit %lu",
+ data->len + 1 + sizeof(lulz)) + 1;
+ git_SHA1_Init(&base);
+ git_SHA1_Update(&base, header, header_len);
+ git_SHA1_Update(&base, data->buf, data->len);
+ git_SHA1_Update(&base, terminator, sizeof(terminator));
+
+#ifdef NO_PTHREADS
+ lulz = find_collision(want, mask, &base, 0, ULONG_MAX);
+#else
+ lulz = find_collision_threaded(online_cpus(), want, mask, &base);
+#endif /* NO_PTHREADS */
+
+ if (lulz != ULONG_MAX) {
+ strbuf_add(data, terminator, sizeof(terminator));
+ strbuf_add(data, &lulz, sizeof(lulz));
+ }
+ else
+ warning("sorry, I couldn't find a collision!");
+}
+
static const char commit_utf8_warn[] =
"Warning: commit message does not conform to UTF-8.\n"
"You may want to amend it after fixing the message, or set the config\n"
@@ -849,6 +1002,15 @@ int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret,
const char *author)
{
+ return commit_tree_collide(msg, tree, parents, ret, author,
+ NULL, NULL);
+}
+
+int commit_tree_collide(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const unsigned char *want,
+ const unsigned char *mask)
+{
int result;
int encoding_is_utf8;
struct strbuf buffer;
@@ -890,6 +1052,9 @@ int commit_tree(const char *msg, unsigned char *tree,
if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn);
+ if (want && mask)
+ collide_commit(&buffer, want, mask);
+
result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
strbuf_release(&buffer);
return result;
diff --git a/commit.h b/commit.h
index 009b113..337dcbd 100644
--- a/commit.h
+++ b/commit.h
@@ -184,5 +184,9 @@ static inline int single_parent(struct commit *commit)
extern int commit_tree(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret,
const char *author);
+extern int commit_tree_collide(const char *msg, unsigned char *tree,
+ struct commit_list *parents, unsigned char *ret,
+ const char *author, const unsigned char *sha1,
+ const unsigned char *mask);
#endif /* COMMIT_H */
--
1.7.7.40.g1a72f
next prev parent reply other threads:[~2011-10-24 20:47 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-10-19 18:03 [IGNORETHIS/PATCH] Choosing the sha1 prefix of your commits Ævar Arnfjörð Bjarmason
2011-10-19 19:01 ` Jeff King
2011-10-19 19:38 ` Jeff King
2011-10-20 2:51 ` Jeff King
2011-10-20 4:15 ` Kyle Moffett
2011-10-20 4:25 ` Jeff King
2011-10-20 4:27 ` Junio C Hamano
2011-10-20 4:32 ` Kyle Moffett
2011-10-24 20:47 ` Jeff King [this message]
2011-10-20 4:31 ` Junio C Hamano
2011-10-20 4:34 ` Jeff King
2011-10-20 6:57 ` Junio C Hamano
2011-10-20 7:13 ` Jeff King
2011-10-20 13:14 ` Ted Ts'o
2011-10-20 15:56 ` Jeff King
2011-10-25 22:35 ` Drew Northup
2011-10-20 18:36 ` Re* " Junio C Hamano
2011-10-20 19:00 ` Jeff King
2011-10-20 7:27 ` Nguyen Thai Ngoc Duy
2011-10-20 9:14 ` Nguyen Thai Ngoc Duy
2011-10-20 15:44 ` Jeff King
2011-10-20 9:38 ` Mikael Magnusson
2011-10-20 13:44 ` Elijah Newren
2011-10-19 22:09 ` Jonathan Nieder
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20111024204737.GA25574@sigill.intra.peff.net \
--to=peff@peff.net \
--cc=avarab@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).