From 9eed52fa40664819c39fa264cc4b15ea06f9b4e5 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Thu, 12 Jul 2001 21:44:01 +0000 Subject: Replaced GList by adding prev/next pointers to LINE_REC. This should make some things faster and take a bit less memory. Also fixed an evil memory leak. git-svn-id: http://svn.irssi.org/repos/irssi/trunk@1611 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- src/fe-text/gui-printtext.c | 2 +- src/fe-text/lastlog.c | 20 +++---- src/fe-text/textbuffer-commands.c | 18 +++---- src/fe-text/textbuffer-view.c | 107 ++++++++++++++++---------------------- src/fe-text/textbuffer-view.h | 6 +-- src/fe-text/textbuffer.c | 86 +++++++++++++++++++++--------- src/fe-text/textbuffer.h | 13 ++--- 7 files changed, 135 insertions(+), 117 deletions(-) (limited to 'src/fe-text') diff --git a/src/fe-text/gui-printtext.c b/src/fe-text/gui-printtext.c index 330eb66c..52cc5c9e 100644 --- a/src/fe-text/gui-printtext.c +++ b/src/fe-text/gui-printtext.c @@ -57,7 +57,7 @@ static void remove_old_lines(TEXT_BUFFER_VIEW_REC *view) scrollback_lines+scrollback_burst_remove) { /* remove lines by line count */ while (view->buffer->lines_count > scrollback_lines) { - line = view->buffer->lines->data; + line = view->buffer->first_line; if (line->info.time >= old_time || scrollback_lines == 0) { /* too new line, don't remove yet - also diff --git a/src/fe-text/lastlog.c b/src/fe-text/lastlog.c index 90464bc2..320a2a99 100644 --- a/src/fe-text/lastlog.c +++ b/src/fe-text/lastlog.c @@ -35,17 +35,19 @@ static void window_lastlog_clear(WINDOW_REC *window) { - TEXT_BUFFER_VIEW_REC *view; - GList *tmp, *next; + TEXT_BUFFER_VIEW_REC *view; + LINE_REC *line, *next; screen_refresh_freeze(); view = WINDOW_GUI(window)->view; - for (tmp = textbuffer_view_get_lines(view); tmp != NULL; tmp = next) { - LINE_REC *line = tmp->data; + line = textbuffer_view_get_lines(view); - next = tmp->next; - if (line->info.level & MSGLEVEL_LASTLOG) + while (line != NULL) { + next = line->next; + + if (line->info.level & MSGLEVEL_LASTLOG) textbuffer_view_remove_line(view, line); + line = next; } textbuffer_view_redraw(view); screen_refresh_thaw(); @@ -131,10 +133,8 @@ static void show_lastlog(const char *searchtext, GHashTable *optlist, else startline = NULL; - if (startline == NULL) { - list = textbuffer_view_get_lines(WINDOW_GUI(window)->view); - startline = list == NULL ? NULL : list->data; - } + if (startline == NULL) + startline = textbuffer_view_get_lines(WINDOW_GUI(window)->view); list = textbuffer_find_text(WINDOW_GUI(window)->view->buffer, startline, level, MSGLEVEL_LASTLOG, diff --git a/src/fe-text/textbuffer-commands.c b/src/fe-text/textbuffer-commands.c index 92ec70bd..24847dac 100644 --- a/src/fe-text/textbuffer-commands.c +++ b/src/fe-text/textbuffer-commands.c @@ -74,13 +74,13 @@ static void scrollback_goto_line(int linenum) if (view->buffer->lines_count == 0) return; - textbuffer_view_scroll_line(view, view->buffer->lines->data); + textbuffer_view_scroll_line(view, view->buffer->first_line); gui_window_scroll(active_win, linenum); } static void scrollback_goto_time(const char *datearg, const char *timearg) { - GList *tmp; + LINE_REC *line; struct tm tm; time_t now, stamp; int day, month; @@ -145,12 +145,10 @@ static void scrollback_goto_time(const char *datearg, const char *timearg) } /* scroll to first line after timestamp */ - tmp = textbuffer_view_get_lines(WINDOW_GUI(active_win)->view); - for (; tmp != NULL; tmp = tmp->next) { - LINE_REC *rec = tmp->data; - - if (rec->info.time >= stamp) { - gui_window_scroll_line(active_win, rec); + line = textbuffer_view_get_lines(WINDOW_GUI(active_win)->view); + for (; line != NULL; line = line->next) { + if (line->info.time >= stamp) { + gui_window_scroll_line(active_win, line); break; } } @@ -188,7 +186,7 @@ static void cmd_scrollback_home(const char *data) buffer = WINDOW_GUI(active_win)->view->buffer; if (buffer->lines_count > 0) - gui_window_scroll_line(active_win, buffer->lines->data); + gui_window_scroll_line(active_win, buffer->first_line); } /* SYNTAX: SCROLLBACK END */ @@ -200,7 +198,7 @@ static void cmd_scrollback_end(const char *data) if (view->bottom_startline == NULL) return; - textbuffer_view_scroll_line(view, view->bottom_startline->data); + textbuffer_view_scroll_line(view, view->bottom_startline); gui_window_scroll(active_win, view->bottom_subline); } diff --git a/src/fe-text/textbuffer-view.c b/src/fe-text/textbuffer-view.c index 1af4220a..aa9f5c2b 100644 --- a/src/fe-text/textbuffer-view.c +++ b/src/fe-text/textbuffer-view.c @@ -321,7 +321,7 @@ static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, original if possible */ static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view) { - GList *tmp; + LINE_REC *line; int linecount, total; if (view->empty_linecount == 0) { @@ -331,12 +331,10 @@ static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view) } total = 0; - tmp = g_list_last(view->buffer->lines); - for (; tmp != NULL; tmp = tmp->prev) { - LINE_REC *line = tmp->data; - + line = textbuffer_line_last(view->buffer); + for (; line != NULL; line = line->prev) { linecount = view_get_linecount(view, line); - if (tmp == view->bottom_startline) { + if (line == view->bottom_startline) { /* keep the old one, make sure that subline is ok */ if (view->bottom_subline > linecount) view->bottom_subline = linecount; @@ -347,7 +345,7 @@ static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view) total += linecount; if (total >= view->height) { - view->bottom_startline = tmp; + view->bottom_startline = line; view->bottom_subline = total - view->height; view->empty_linecount = 0; return; @@ -355,20 +353,20 @@ static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view) } /* not enough lines so we must be at the beginning of the buffer */ - view->bottom_startline = view->buffer->lines; + view->bottom_startline = view->buffer->first_line; view->bottom_subline = 0; view->empty_linecount = view->height - total; } static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view) { - GList *tmp; + LINE_REC *line; g_return_if_fail(view != NULL); view->ypos = -view->subline-1; - for (tmp = view->startline; tmp != NULL; tmp = tmp->next) - view->ypos += view_get_linecount(view, tmp->data); + for (line = view->startline; line != NULL; line = line->next) + view->ypos += view_get_linecount(view, line); } /* Create new view. */ @@ -445,28 +443,26 @@ void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view, view->longword_noindent = longword_noindent; } -static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, GList *lines) +static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { int linecount; linecount = 0; - while (lines != NULL) { - linecount += view_get_linecount(view, lines->data); - lines = lines->next; + while (line != NULL) { + linecount += view_get_linecount(view, line); + line = line->next; } return linecount; } -static void view_draw(TEXT_BUFFER_VIEW_REC *view, GList *line, +static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, int subline, int ypos, int lines) { int linecount; while (line != NULL && lines > 0) { - LINE_REC *rec = line->data; - - linecount = view_line_draw(view, rec, subline, ypos, lines); + linecount = view_line_draw(view, line, subline, ypos, lines); ypos += linecount; lines -= linecount; subline = 0; @@ -486,13 +482,13 @@ static void view_draw(TEXT_BUFFER_VIEW_REC *view, GList *line, static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines) { - GList *line; + LINE_REC *line; int ypos, maxline, subline, linecount; maxline = view->height-lines; line = view->startline; ypos = -view->subline; subline = 0; while (line != NULL && ypos < maxline) { - linecount = view_get_linecount(view, line->data); + linecount = view_get_linecount(view, line); ypos += linecount; if (ypos > maxline) { subline = maxline-(ypos-linecount); @@ -505,8 +501,8 @@ static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines) } /* Returns number of lines actually scrolled */ -static int view_scroll(TEXT_BUFFER_VIEW_REC *view, GList **lines, int *subline, - int scrollcount, int draw_nonclean) +static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines, + int *subline, int scrollcount, int draw_nonclean) { int linecount, realcount, scroll_visible; @@ -520,7 +516,7 @@ static int view_scroll(TEXT_BUFFER_VIEW_REC *view, GList **lines, int *subline, scrollcount += *subline; *subline = 0; while (scrollcount > 0) { - linecount = view_get_linecount(view, (*lines)->data); + linecount = view_get_linecount(view, *lines); if ((scroll_visible && *lines == view->bottom_startline) && (scrollcount >= view->bottom_subline)) { @@ -545,7 +541,7 @@ static int view_scroll(TEXT_BUFFER_VIEW_REC *view, GList **lines, int *subline, /* scroll up */ while (scrollcount < 0 && (*lines)->prev != NULL) { *lines = (*lines)->prev; - linecount = view_get_linecount(view, (*lines)->data); + linecount = view_get_linecount(view, *lines); realcount -= linecount; scrollcount += linecount; @@ -595,7 +591,7 @@ void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height) view->width = width; view->height = height; - if (view->buffer->lines == NULL) { + if (view->buffer->first_line == NULL) { view->empty_linecount = height; return; } @@ -603,8 +599,8 @@ void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height) textbuffer_view_init_bottom(view); /* check that we didn't scroll lower than bottom startline.. */ - if (g_list_find(view->bottom_startline->next, - view->startline->data) != NULL) { + if (textbuffer_line_exists_after(view->bottom_startline->next, + view->startline)) { view->startline = view->bottom_startline; view->subline = view->bottom_subline; } else if (view->startline == view->bottom_startline && @@ -612,7 +608,7 @@ void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height) view->subline = view->bottom_subline; } else { /* make sure the subline is still in allowed range */ - linecount = view_get_linecount(view, view->startline->data); + linecount = view_get_linecount(view, view->startline); if (view->subline > linecount) view->subline = linecount; } @@ -649,7 +645,7 @@ void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view) view->ypos = -1; view->bottom_startline = view->startline = - g_list_last(view->buffer->lines); + textbuffer_line_last(view->buffer); view->bottom_subline = view->subline = view->buffer->cur_line == NULL ? 0 : view_get_linecount(view, view->buffer->cur_line); @@ -678,23 +674,14 @@ void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines) /* Scroll to specified line */ void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) { - GList *tmp; - g_return_if_fail(view != NULL); - if (g_list_find(view->bottom_startline->next, line) != NULL) { + if (textbuffer_line_exists_after(view->bottom_startline->next, line)) { view->startline = view->bottom_startline; view->subline = view->bottom_subline; } else { - for (tmp = view->buffer->lines; tmp != NULL; tmp = tmp->next) { - LINE_REC *rec = tmp->data; - - if (rec == line) { - view->startline = tmp; - view->subline = 0; - break; - } - } + view->startline = line; + view->subline = 0; } textbuffer_view_init_ypos(view); @@ -752,11 +739,11 @@ static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) if (view->bottom_startline == NULL) { view->startline = view->bottom_startline = - view->buffer->lines; + view->buffer->first_line; } if (view->buffer->cur_line != line && - g_list_find(view->bottom_startline, line) == NULL) + !textbuffer_line_exists_after(view->bottom_startline, line)) return; linecount = view->cache->last_linecount; @@ -850,11 +837,8 @@ static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) (GHFunc) bookmark_check_remove, &rec); if (rec.remove_list != NULL) { - GList *pos = g_list_find(view->buffer->lines, line); - - new_line = pos == NULL || pos->prev == NULL ? NULL : - (pos->next == NULL ? pos->prev->data : - pos->next->data); + new_line = line->prev == NULL ? NULL : + (line->next == NULL ? line->prev : line->next); for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) { g_hash_table_remove(view->bookmarks, tmp->data); if (new_line != NULL) { @@ -869,20 +853,18 @@ static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line) /* Return number of real lines `lines' list takes - stops counting when the height reaches the view height */ static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view, - GList *lines, int subline, + LINE_REC *line, int subline, LINE_REC *skip_line) { int height, linecount; height = -subline; - while (lines != NULL && height < view->height) { - LINE_REC *line = lines->data; - + while (line != NULL && height < view->height) { if (line != skip_line) { linecount = view_get_linecount(view, line); height += linecount; } - lines = lines->next; + line = line->next; } return height < view->height ? height : view->height; @@ -899,22 +881,22 @@ static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, /* the last line is being removed */ LINE_REC *prevline; - prevline = view->buffer->lines->data == line ? NULL : - g_list_last(view->bottom_startline)->data; + prevline = view->buffer->first_line == line ? NULL : + textbuffer_line_last(view->buffer); view->cache->last_linecount = prevline == NULL ? 0 : view_get_linecount(view, prevline); } - if (line == view->buffer->lines->data) { + if (line == view->buffer->first_line) { /* first line in the buffer - this is the most commonly removed line.. */ - if (view->bottom_startline->data == line) { + if (view->bottom_startline == line) { /* very small scrollback.. */ view->bottom_startline = view->bottom_startline->next; view->bottom_subline = 0; } - if (view->startline->data == line) { + if (view->startline == line) { /* removing the first line in screen */ realcount = view_scroll(view, &view->startline, &view->subline, @@ -922,7 +904,8 @@ static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, view->ypos -= realcount; view->empty_linecount += linecount-realcount; } - } else if (g_list_find(view->bottom_startline, line) != NULL) { + } else if (textbuffer_line_exists_after(view->bottom_startline, + line)) { realcount = view_scroll(view, &view->bottom_startline, &view->bottom_subline, -linecount, FALSE); @@ -933,7 +916,7 @@ static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line, &view->subline, -linecount, TRUE); view->ypos -= linecount-realcount; } else { - if (view->startline->data == line) { + if (view->startline == line) { view->startline = view->startline->next != NULL ? view->startline->next : @@ -1037,7 +1020,7 @@ void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view, g_return_if_fail(name != NULL); if (view->bottom_startline != NULL) { - line = g_list_last(view->bottom_startline)->data; + line = textbuffer_line_last(view->buffer); textbuffer_view_set_bookmark(view, name, line); } } diff --git a/src/fe-text/textbuffer-view.h b/src/fe-text/textbuffer-view.h index 54e05686..51973acf 100644 --- a/src/fe-text/textbuffer-view.h +++ b/src/fe-text/textbuffer-view.h @@ -50,11 +50,11 @@ typedef struct { TEXT_BUFFER_CACHE_REC *cache; int ypos; /* cursor position - visible area is 0..height-1 */ - GList *startline; /* line at the top of the screen */ + LINE_REC *startline; /* line at the top of the screen */ int subline; /* number of "real lines" to skip from `startline' */ /* marks the bottom of the text buffer */ - GList *bottom_startline; + LINE_REC *bottom_startline; int bottom_subline; /* how many empty lines are in screen. a screenful when started @@ -86,7 +86,7 @@ void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height); void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view); #define textbuffer_view_get_lines(view) \ - ((view)->buffer->lines) + ((view)->buffer->first_line) /* Scroll the view up/down */ void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines); diff --git a/src/fe-text/textbuffer.c b/src/fe-text/textbuffer.c index f97f46c7..06cb2fe9 100644 --- a/src/fe-text/textbuffer.c +++ b/src/fe-text/textbuffer.c @@ -187,14 +187,23 @@ static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer, { LINE_REC *line; - line = textbuffer_line_create(buffer); - if (prev == buffer->cur_line) { - buffer->cur_line = line; - buffer->lines = g_list_append(buffer->lines, buffer->cur_line); + line = textbuffer_line_create(buffer); + line->prev = prev; + if (prev == NULL) { + line->next = buffer->first_line; + if (buffer->first_line != NULL) + buffer->first_line->prev = line; + buffer->first_line = line; } else { - buffer->lines = g_list_insert(buffer->lines, line, - g_list_index(buffer->lines, prev)+1); + line->prev = prev; + line->next = prev->next; + if (line->next != NULL) + line->next->prev = line; + prev->next = line; } + + if (prev == buffer->cur_line) + buffer->cur_line = line; buffer->lines_count++; return line; @@ -229,6 +238,28 @@ void textbuffer_line_unref_list(TEXT_BUFFER_REC *buffer, GList *list) } } +LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer) +{ + LINE_REC *line; + + line = buffer->cur_line; + if (line != NULL) { + while (line->next != NULL) + line = line->next; + } + return line; +} + +int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search) +{ + while (line != NULL) { + if (line == search) + return TRUE; + line = line->next; + } + return FALSE; +} + LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer, const unsigned char *data, int len, LINE_INFO_REC *info) @@ -264,11 +295,16 @@ void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line) g_return_if_fail(buffer != NULL); g_return_if_fail(line != NULL); - buffer->lines = g_list_remove(buffer->lines, line); + if (buffer->first_line == line) + buffer->first_line = line->next; + if (line->prev != NULL) + line->prev->next = line->next; + if (line->next != NULL) + line->next->prev = line->prev; if (buffer->cur_line == line) { - buffer->cur_line = buffer->lines == NULL ? NULL : - g_list_last(buffer->lines)->data; + buffer->cur_line = line->next != NULL ? + line->next : line->prev; } buffer->lines_count--; @@ -279,6 +315,7 @@ void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line) void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer) { GSList *tmp; + LINE_REC *line; g_return_if_fail(buffer != NULL); @@ -287,8 +324,11 @@ void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer) g_slist_free(buffer->text_chunks); buffer->text_chunks = NULL; - g_list_free(buffer->lines); - buffer->lines = NULL; + while (buffer->first_line != NULL) { + line = buffer->first_line->next; + g_mem_chunk_free(line_chunk, buffer->first_line); + buffer->first_line = line; + } buffer->cur_line = NULL; buffer->lines_count = 0; @@ -365,7 +405,7 @@ GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline, #ifdef HAVE_REGEX_H regex_t preg; #endif - GList *line, *tmp; + LINE_REC *line; GList *matches; GString *str; @@ -386,25 +426,21 @@ GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline, matches = NULL; str = g_string_new(NULL); - line = g_list_find(buffer->lines, startline); - if (line == NULL) - line = buffer->lines; - - for (tmp = line; tmp != NULL; tmp = tmp->next) { - LINE_REC *rec = tmp->data; + line = startline != NULL ? startline : buffer->first_line; - if ((rec->info.level & level) == 0 || - (rec->info.level & nolevel) != 0) + for (; line != NULL; line = line->next) { + if ((line->info.level & level) == 0 || + (line->info.level & nolevel) != 0) continue; if (*text == '\0') { /* no search word, everything matches */ - textbuffer_line_ref(rec); - matches = g_list_append(matches, rec); + textbuffer_line_ref(line); + matches = g_list_append(matches, line); continue; } - textbuffer_line2text(rec, FALSE, str); + textbuffer_line2text(line, FALSE, str); if ( #ifdef HAVE_REGEX_H @@ -415,8 +451,8 @@ GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline, case_sensitive ? strstr(str->str, text) != NULL : stristr(str->str, text) != NULL) { /* matched */ - textbuffer_line_ref(rec); - matches = g_list_append(matches, rec); + textbuffer_line_ref(line); + matches = g_list_append(matches, line); } } #ifdef HAVE_REGEX_H diff --git a/src/fe-text/textbuffer.h b/src/fe-text/textbuffer.h index 21f70e26..ddbfd72c 100644 --- a/src/fe-text/textbuffer.h +++ b/src/fe-text/textbuffer.h @@ -1,10 +1,6 @@ #ifndef __TEXTBUFFER_H #define __TEXTBUFFER_H -/* FIXME: Textbuffer code gets a lot faster in some points when I get rid of - GList and make prev/next pointers directly in LINE_REC. However, this - can still wait for a while until I get rid of GList entirely everywhere. */ - #define LINE_TEXT_CHUNK_SIZE 16384 enum { @@ -27,13 +23,15 @@ typedef struct { time_t time; } LINE_INFO_REC; -typedef struct { +typedef struct _LINE_REC { /* text in the line. \0 means that the next char will be a color or command. <= 127 = color or if 8. bit is set, the first 7 bits are the command. See LINE_CMD_xxxx. DO NOT ADD BLACK WITH \0\0 - this will break things. Use LINE_CMD_COLOR0 instead. */ + struct _LINE_REC *prev, *next; + unsigned char *text; unsigned char refcount; LINE_INFO_REC info; @@ -47,7 +45,7 @@ typedef struct { typedef struct { GSList *text_chunks; - GList *lines; + LINE_REC *first_line; int lines_count; LINE_REC *cur_line; @@ -65,6 +63,9 @@ void textbuffer_line_ref(LINE_REC *line); void textbuffer_line_unref(TEXT_BUFFER_REC *buffer, LINE_REC *line); void textbuffer_line_unref_list(TEXT_BUFFER_REC *buffer, GList *list); +LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer); +int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search); + /* Append text to buffer. When \0 is found at the END OF DATA, a new line is created. You must send the EOL command before you can do anything else with the buffer. */ -- cgit v1.2.3