dri-devel Archive on lore.kernel.org
 help / color / Atom feed
From: Max Staudt <mstaudt@suse.de>
To: b.zolnierkie@samsung.com, linux-fbdev@vger.kernel.org
Cc: michal@markovi.net, sndirsch@suse.com, oneukum@suse.com,
	tiwai@suse.com, dri-devel@lists.freedesktop.org,
	linux-kernel@vger.kernel.org, mstaudt@suse.de,
	bernhard.rosenkranzer@linaro.org, philm@manjaro.org
Subject: [RFC PATCH v2 12/13] tools/bootsplash: Add a basic splash file creation tool
Date: Wed, 13 Dec 2017 20:47:54 +0100
Message-ID: <20171213194755.3409-13-mstaudt@suse.de> (raw)
In-Reply-To: <20171213194755.3409-1-mstaudt@suse.de>

Signed-off-by: Max Staudt <mstaudt@suse.de>
---
 MAINTAINERS                          |   1 +
 tools/bootsplash/.gitignore          |   1 +
 tools/bootsplash/Makefile            |   9 +
 tools/bootsplash/bootsplash-packer.c | 471 +++++++++++++++++++++++++++++++++++
 4 files changed, 482 insertions(+)
 create mode 100644 tools/bootsplash/.gitignore
 create mode 100644 tools/bootsplash/Makefile
 create mode 100644 tools/bootsplash/bootsplash-packer.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 7ffac272434e..ddff07cd794c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2715,6 +2715,7 @@ F:	drivers/video/fbdev/core/bootsplash*.*
 F:	drivers/video/fbdev/core/dummycon.c
 F:	include/linux/bootsplash.h
 F:	include/uapi/linux/bootsplash_file.h
+F:	tools/bootsplash/*
 
 BPF (Safe dynamic programs and tools)
 M:	Alexei Starovoitov <ast@kernel.org>
diff --git a/tools/bootsplash/.gitignore b/tools/bootsplash/.gitignore
new file mode 100644
index 000000000000..091b99a17567
--- /dev/null
+++ b/tools/bootsplash/.gitignore
@@ -0,0 +1 @@
+bootsplash-packer
diff --git a/tools/bootsplash/Makefile b/tools/bootsplash/Makefile
new file mode 100644
index 000000000000..0ad8e8a84942
--- /dev/null
+++ b/tools/bootsplash/Makefile
@@ -0,0 +1,9 @@
+CC := $(CROSS_COMPILE)gcc
+CFLAGS := -I../../usr/include
+
+PROGS := bootsplash-packer
+
+all: $(PROGS)
+
+clean:
+	rm -fr $(PROGS)
diff --git a/tools/bootsplash/bootsplash-packer.c b/tools/bootsplash/bootsplash-packer.c
new file mode 100644
index 000000000000..ffb6a8b69885
--- /dev/null
+++ b/tools/bootsplash/bootsplash-packer.c
@@ -0,0 +1,471 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Splash file packer tool)
+ *
+ * Authors:
+ * Max Staudt <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <endian.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/bootsplash_file.h>
+
+
+static void print_help(char *progname)
+{
+	printf("Usage: %s [OPTIONS] outfile\n", progname);
+	printf("\n"
+	       "Options, executed in order given:\n"
+	       "  -h, --help                   Print this help message\n"
+	       "\n"
+	       "  --bg_red <u8>                Background color (red part)\n"
+	       "  --bg_green <u8>              Background color (green part)\n"
+	       "  --bg_blue <u8>               Background color (blue part)\n"
+	       "  --bg_reserved <u8>           (do not use)\n"
+	       "  --frame_ms <u16>             Minimum milliseconds between animation steps\n"
+	       "\n"
+	       "  --picture                    Start describing the next picture\n"
+	       "  --pic_width <u16>            Picture width in pixels\n"
+	       "  --pic_height <u16>           Picture height in pixels\n"
+	       "  --pic_position <u8>             Coarse picture placement:\n"
+	       "                                  0x00 - Top left\n"
+	       "                                  0x01 - Top\n"
+	       "                                  0x02 - Top right\n"
+	       "                                  0x03 - Right\n"
+	       "                                  0x04 - Bottom right\n"
+	       "                                  0x05 - Bottom\n"
+	       "                                  0x06 - Bottom left\n"
+	       "                                  0x07 - Left\n"
+	       "\n"
+	       "                                Flags:\n"
+	       "                                 0x10 - Calculate offset from corner towards center,\n"
+	       "                                         rather than from center towards corner\n"
+	       "  --pic_position_offset <u16>  Distance from base position in pixels\n"
+	       "  --pic_anim_type <u8>         Animation type:\n"
+	       "                                 0 - None\n"
+	       "                                 1 - Forward loop\n"
+	       "  --pic_anim_loop <u8>         Loop point for animation\n"
+	       "\n"
+	       "  --blob <filename>            Include next data stream\n"
+	       "  --blob_type <u16>            Type of data\n"
+	       "  --blob_picture_id <u8>       Picture to associate this blob with, starting at 0\n"
+	       "                                 (default: number of last --picture)\n"
+	       "\n");
+	printf("This tool will write %s files.\n\n",
+#if __BYTE_ORDER == __BIG_ENDIAN
+	       "Big Endian (BE)");
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+	       "Little Endian (LE)");
+#else
+#error
+#endif
+}
+
+
+struct blob_entry {
+	struct blob_entry *next;
+
+	char *fn;
+
+	struct splash_blob_header header;
+};
+
+
+static void dump_file_header(struct splash_file_header *h)
+{
+	printf(" --- File header ---\n");
+	printf("\n");
+	printf("  version:     %5u\n", h->version);
+	printf("\n");
+	printf("  bg_red:      %5u\n", h->bg_red);
+	printf("  bg_green:    %5u\n", h->bg_green);
+	printf("  bg_blue:     %5u\n", h->bg_blue);
+	printf("  bg_reserved: %5u\n", h->bg_reserved);
+	printf("\n");
+	printf("  num_blobs:   %5u\n", h->num_blobs);
+	printf("  num_pics:    %5u\n", h->num_pics);
+	printf("\n");
+	printf("  frame_ms:    %5u\n", h->frame_ms);
+	printf("\n");
+}
+
+static void dump_pic_header(struct splash_pic_header *ph)
+{
+	printf(" --- Picture header ---\n");
+	printf("\n");
+	printf("  width:           %5u\n", ph->width);
+	printf("  height:          %5u\n", ph->height);
+	printf("\n");
+	printf("  num_blobs:       %5u\n", ph->num_blobs);
+	printf("\n");
+	printf("  position:        %0x3x\n", ph->position);
+	printf("  position_offset: %5u\n", ph->position_offset);
+	printf("\n");
+	printf("  anim_type:       %5u\n", ph->anim_type);
+	printf("  anim_loop:       %5u\n", ph->anim_loop);
+	printf("\n");
+}
+
+static void dump_blob(struct blob_entry *b)
+{
+	printf(" --- Blob header ---\n");
+	printf("\n");
+	printf("  length:     %7u\n", b->header.length);
+	printf("  type:       %7u\n", b->header.type);
+	printf("\n");
+	printf("  picture_id: %7u\n", b->header.picture_id);
+	printf("\n");
+}
+
+
+#define OPT_MAX(var, max) \
+	do { \
+		if ((var) > max) { \
+			fprintf(stderr, "--%s: Invalid value\n", \
+			long_options[option_index].name); \
+			break; \
+		} \
+	} while (0)
+
+static struct option long_options[] = {
+	{"help", 0, 0, 'h'},
+	{"bg_red", 1, 0, 10001},
+	{"bg_green", 1, 0, 10002},
+	{"bg_blue", 1, 0, 10003},
+	{"bg_reserved", 1, 0, 10004},
+	{"frame_ms", 1, 0, 10005},
+	{"picture", 0, 0, 20000},
+	{"pic_width", 1, 0, 20001},
+	{"pic_height", 1, 0, 20002},
+	{"pic_position", 1, 0, 20003},
+	{"pic_position_offset", 1, 0, 20004},
+	{"pic_anim_type", 1, 0, 20005},
+	{"pic_anim_loop", 1, 0, 20006},
+	{"blob", 1, 0, 30000},
+	{"blob_type", 1, 0, 30001},
+	{"blob_picture_id", 1, 0, 30002},
+	{NULL, 0, NULL, 0}
+};
+
+
+int main(int argc, char **argv)
+{
+	FILE *of;
+	char *ofn;
+	int c;
+	int option_index = 0;
+
+	unsigned long ul;
+	struct splash_file_header fh = {};
+	struct splash_pic_header ph[255];
+	struct blob_entry *blob_first = NULL;
+	struct blob_entry *blob_last = NULL;
+	struct blob_entry *blob_cur = NULL;
+
+	if (argc < 2) {
+		print_help(argv[0]);
+		return EXIT_FAILURE;
+	}
+
+
+	/* Parse and and execute user commands */
+	while ((c = getopt_long(argc, argv, "h",
+			  long_options, &option_index)) != -1) {
+		switch (c) {
+		case 10001:	/* bg_red */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			fh.bg_red = ul;
+			break;
+		case 10002:	/* bg_green */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			fh.bg_green = ul;
+			break;
+		case 10003:	/* bg_blue */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			fh.bg_blue = ul;
+			break;
+		case 10004:	/* bg_reserved */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			fh.bg_reserved = ul;
+			break;
+		case 10005:	/* frame_ms */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 65535);
+			fh.frame_ms = ul;
+			break;
+
+
+		case 20000:	/* picture */
+			if (fh.num_pics >= 255) {
+				fprintf(stderr, "--%s: Picture array full\n",
+					long_options[option_index].name);
+				break;
+			}
+
+			fh.num_pics++;
+			break;
+
+		case 20001:	/* pic_width */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 65535);
+			ph[fh.num_pics - 1].width = ul;
+			break;
+
+		case 20002:	/* pic_height */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 65535);
+			ph[fh.num_pics - 1].height = ul;
+			break;
+
+		case 20003:	/* pic_position */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			ph[fh.num_pics - 1].position = ul;
+			break;
+
+		case 20004:	/* pic_position_offset */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			ph[fh.num_pics - 1].position_offset = ul;
+			break;
+
+		case 20005:	/* pic_anim_type */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			ph[fh.num_pics - 1].anim_type = ul;
+			break;
+
+		case 20006:	/* pic_anim_loop */
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			ph[fh.num_pics - 1].anim_loop = ul;
+			break;
+
+
+		case 30000:	/* blob */
+			if (fh.num_blobs >= 65535) {
+				fprintf(stderr, "--%s: Blob array full\n",
+					long_options[option_index].name);
+				break;
+			}
+
+			blob_cur = calloc(1, sizeof(struct blob_entry));
+			if (!blob_cur) {
+				fprintf(stderr, "--%s: Out of memory\n",
+					long_options[option_index].name);
+				break;
+			}
+
+			blob_cur->fn = optarg;
+			if (fh.num_pics)
+				blob_cur->header.picture_id = fh.num_pics - 1;
+
+			if (!blob_first)
+				blob_first = blob_cur;
+			if (blob_last)
+				blob_last->next = blob_cur;
+			blob_last = blob_cur;
+			fh.num_blobs++;
+			break;
+
+		case 30001:	/* blob_type */
+			if (!blob_cur) {
+				fprintf(stderr, "--%s: No blob selected\n",
+					long_options[option_index].name);
+				break;
+			}
+
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			blob_cur->header.type = ul;
+			break;
+
+		case 30002:	/* blob_picture_id */
+			if (!blob_cur) {
+				fprintf(stderr, "--%s: No blob selected\n",
+					long_options[option_index].name);
+				break;
+			}
+
+			ul = strtoul(optarg, NULL, 0);
+			OPT_MAX(ul, 255);
+			blob_cur->header.picture_id = ul;
+			break;
+
+
+
+		case 'h':
+		case '?':
+		default:
+			print_help(argv[0]);
+			goto EXIT;
+		} /* switch (c) */
+	} /* while ((c = getopt_long(...)) != -1) */
+
+	/* Consume and drop lone arguments */
+	while (optind < argc) {
+		ofn = argv[optind];
+		optind++;
+	}
+
+
+	/* Read file lengths */
+	for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) {
+		FILE *f;
+		long pos;
+		int i;
+
+		if (!blob_cur->fn)
+			continue;
+
+		f = fopen(blob_cur->fn, "rb");
+		if (!f)
+			goto ERR_FILE_LEN;
+
+		if (fseek(f, 0, SEEK_END))
+			goto ERR_FILE_LEN;
+
+		pos = ftell(f);
+		if (pos < 0 || pos > (1 << 30))
+			goto ERR_FILE_LEN;
+
+		blob_cur->header.length = pos;
+
+		fclose(f);
+		continue;
+
+ERR_FILE_LEN:
+		fprintf(stderr, "Error getting file length (or too long): %s\n",
+			blob_cur->fn);
+		if (f)
+			fclose(f);
+		continue;
+	}
+
+
+	/* Set magic headers */
+#if __BYTE_ORDER == __BIG_ENDIAN
+	memcpy(&fh.id[0], BOOTSPLASH_MAGIC_BE, 16);
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+	memcpy(&fh.id[0], BOOTSPLASH_MAGIC_LE, 16);
+#else
+#error
+#endif
+	fh.version = BOOTSPLASH_VERSION;
+
+	/* Set blob counts */
+	for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) {
+		if (blob_cur->header.picture_id < fh.num_pics)
+			ph[blob_cur->header.picture_id].num_blobs++;
+	}
+
+
+	/* Dump structs */
+	dump_file_header(&fh);
+
+	for (ul = 0; ul < fh.num_pics; ul++)
+		dump_pic_header(&ph[ul]);
+
+	for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next)
+		dump_blob(blob_cur);
+
+
+	/* Write to file */
+	printf("Writing splash to file: %s\n", ofn);
+	of = fopen(ofn, "wb");
+	if (!of)
+		goto ERR_WRITING;
+
+	if (fwrite(&fh, sizeof(struct splash_file_header), 1, of) != 1)
+		goto ERR_WRITING;
+
+	for (ul = 0; ul < fh.num_pics; ul++) {
+		if (fwrite(&ph[ul], sizeof(struct splash_pic_header), 1, of)
+		    != 1)
+			goto ERR_WRITING;
+	}
+
+	blob_cur = blob_first;
+	while (blob_cur) {
+		struct blob_entry *blob_old = blob_cur;
+		FILE *f;
+		char *buf[256];
+		uint32_t left;
+
+		if (fwrite(&blob_cur->header,
+			   sizeof(struct splash_blob_header), 1, of) != 1)
+			goto ERR_WRITING;
+
+		if (!blob_cur->header.length || !blob_cur->fn)
+			continue;
+
+		f = fopen(blob_cur->fn, "rb");
+		if (!f)
+			goto ERR_FILE_COPY;
+
+		left = blob_cur->header.length;
+		while (left >= sizeof(buf)) {
+			if (fread(buf, sizeof(buf), 1, f) != 1)
+				goto ERR_FILE_COPY;
+			if (fwrite(buf, sizeof(buf), 1, of) != 1)
+				goto ERR_FILE_COPY;
+			left -= sizeof(buf);
+		}
+		if (left) {
+			if (fread(buf, left, 1, f) != 1)
+				goto ERR_FILE_COPY;
+			if (fwrite(buf, left, 1, of) != 1)
+				goto ERR_FILE_COPY;
+		}
+
+		/* Pad data stream to 16 bytes */
+		if (left % 16) {
+			if (fwrite("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
+					16 - (left % 16), 1, of) != 1)
+				goto ERR_FILE_COPY;
+		}
+
+		fclose(f);
+		blob_cur = blob_cur->next;
+		free(blob_old);
+		continue;
+
+ERR_FILE_COPY:
+		if (f)
+			fclose(f);
+		goto ERR_WRITING;
+	}
+
+	fclose(of);
+
+EXIT:
+	return EXIT_SUCCESS;
+
+
+ERR_WRITING:
+	fprintf(stderr, "Error writing splash.\n");
+	fprintf(stderr, "The output file is probably corrupt.\n");
+	if (of)
+		fclose(of);
+
+	while (blob_cur) {
+		struct blob_entry *blob_old = blob_cur;
+
+		blob_cur = blob_cur->next;
+		free(blob_old);
+	}
+
+	return EXIT_FAILURE;
+}
-- 
2.12.3

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

  parent reply index

Thread overview: 68+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-12-13 19:47 [RFC PATCH v2 00/13] Kernel based bootsplash Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 01/13] bootsplash: Initial implementation showing black screen Max Staudt
2017-12-13 23:55   ` Randy Dunlap
2017-12-14 15:37     ` Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 02/13] bootsplash: Add file reading and picture rendering Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 03/13] bootsplash: Flush framebuffer after drawing Max Staudt
2017-12-13 21:35   ` Daniel Vetter
2017-12-14 15:36     ` Max Staudt
2017-12-19 12:23       ` Daniel Vetter
2017-12-19 13:34         ` Max Staudt
2017-12-19 13:57           ` Daniel Vetter
2017-12-19 14:07             ` Oliver Neukum
2017-12-31 12:53               ` Alan Cox
2018-01-03 18:04                 ` Max Staudt
2017-12-19 15:41             ` Max Staudt
2017-12-19 16:02               ` Daniel Vetter
2017-12-19 16:23                 ` Max Staudt
2017-12-20  9:45                   ` Daniel Vetter
2017-12-19 16:09               ` Daniel Vetter
2017-12-19 16:26                 ` Max Staudt
2017-12-19 21:01               ` Ray Strode
2017-12-20 13:14                 ` Max Staudt
2017-12-20 15:35                   ` Ray Strode
2017-12-20 16:52                     ` Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 04/13] bootsplash: Add corner positioning Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 05/13] bootsplash: Add animation support Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 06/13] vt: Redraw bootsplash fully on console_unblank Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 07/13] vt: Add keyboard hook to disable bootsplash Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 08/13] sysrq: Disable bootsplash on SAK Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 09/13] fbcon: Disable bootsplash on oops Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 10/13] Documentation: Add bootsplash main documentation Max Staudt
2017-12-13 19:47 ` [RFC PATCH v2 11/13] bootsplash: sysfs entries to load and unload files Max Staudt
2017-12-13 19:47 ` Max Staudt [this message]
2017-12-13 19:47 ` [RFC PATCH v2 13/13] tools/bootsplash: Add script and data to create sample file Max Staudt
2017-12-13 19:52 ` [RFC PATCH v2 00/13] Kernel based bootsplash Max Staudt
2017-12-19 16:16 ` Daniel Vetter
2017-12-19 17:04   ` Max Staudt
2017-12-19 17:26     ` Daniel Vetter
2017-12-19 18:40       ` Max Staudt
2017-12-20  9:43         ` Daniel Vetter
2017-12-20 10:06           ` Neil Armstrong
2017-12-20 10:14             ` Daniel Vetter
2017-12-20 14:55               ` Max Staudt
2017-12-20 15:11                 ` Daniel Vetter
2017-12-20 15:19                   ` Daniel Vetter
2017-12-20 15:22                     ` Daniel Vetter
2017-12-20 16:23                     ` Max Staudt
2017-12-20 16:15                   ` Max Staudt
2017-12-31 12:44                   ` Alan Cox
2018-01-03 18:00                     ` Max Staudt
2017-12-20 14:16             ` Max Staudt
2017-12-20 14:10           ` Max Staudt
2017-12-31 12:35         ` Alan Cox
2018-01-03 17:56           ` Max Staudt
2017-12-19 20:30     ` Ray Strode
2017-12-20 13:03       ` Max Staudt
2017-12-20 15:21         ` Ray Strode
2017-12-20 16:44           ` Max Staudt
2017-12-21 14:51             ` Ray Strode
2017-12-21 16:32               ` Max Staudt
2017-12-20 11:08   ` Johannes Thumshirn
2017-12-20 11:22     ` Daniel Stone
2017-12-20 12:48       ` Johannes Thumshirn
2017-12-29 17:13   ` Jani Nikula
2018-01-03 17:38     ` Max Staudt
2017-12-21  9:48 ` Daniel Vetter
2017-12-21 16:52   ` Max Staudt
2017-12-21 15:00 ` Philip Müller

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=20171213194755.3409-13-mstaudt@suse.de \
    --to=mstaudt@suse.de \
    --cc=b.zolnierkie@samsung.com \
    --cc=bernhard.rosenkranzer@linaro.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=linux-fbdev@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=michal@markovi.net \
    --cc=oneukum@suse.com \
    --cc=philm@manjaro.org \
    --cc=sndirsch@suse.com \
    --cc=tiwai@suse.com \
    /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

dri-devel Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/dri-devel/0 dri-devel/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 dri-devel dri-devel/ https://lore.kernel.org/dri-devel \
		dri-devel@lists.freedesktop.org
	public-inbox-index dri-devel

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.freedesktop.lists.dri-devel


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git