git-check-ref-format purehelpers
git-cherry ancillaryinterrogators
git-cherry-pick mainporcelain
+git-citool mainporcelain
git-clean mainporcelain
git-clone mainporcelain
git-commit mainporcelain
git-gc mainporcelain
git-get-tar-commit-id ancillaryinterrogators
git-grep mainporcelain
+git-gui mainporcelain
git-hash-object plumbingmanipulators
git-http-fetch synchelpers
git-http-push synchelpers
--- /dev/null
+git-citool(1)
+=============
+
+NAME
+----
+git-citool - Graphical alternative to git-commit
+
+SYNOPSIS
+--------
+'git citool'
+
+DESCRIPTION
+-----------
+A Tcl/Tk based graphical interface to review modified files, stage
+them into the index, enter a commit message and record the new
+commit onto the current branch. This interface is an alternative
+to the less interactive gitlink:git-commit[1] program.
+
+git-citool is actually a standard alias for 'git gui citool'.
+See gitlink:git-gui[1] for more details.
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
--- /dev/null
+git-gui(1)
+==========
+
+NAME
+----
+git-gui - A portable graphical interface to Git
+
+SYNOPSIS
+--------
+'git gui' [<command>] [arguments]
+
+DESCRIPTION
+-----------
+A Tcl/Tk based graphical user interface to Git. git-gui focuses
+on allowing users to make changes to their repository by making
+new commits, amending existing ones, creating branches, performing
+local merges, and fetching/pushing to remote repositories.
+
+Unlike gitlink:gitk[1], git-gui focuses on commit generation
+and single file annotation, and does not show project history.
+It does however supply menu actions to start a gitk session from
+within git-gui.
+
+git-gui is known to work on all popular UNIX systems, Mac OS X,
+and Windows (under both Cygwin and MSYS). To the extent possible
+OS specific user interface guidelines are followed, making git-gui
+a fairly native interface for users.
+
+COMMANDS
+--------
+blame::
+ Start a blame viewer on the specified file on the given
+ version (or working directory if not specified).
+
+browser::
+ Start a tree browser showing all files in the specified
+ commit (or 'HEAD' by default). Files selected through the
+ browser are opened in the blame viewer.
+
+citool::
+ Start git-gui and arrange to make exactly one commit before
+ exiting and returning to the shell. The interface is limited
+ to only commit actions, slightly reducing the application's
+ startup time and simplifying the menubar.
+
+version::
+ Display the currently running version of git-gui.
+
+
+Examples
+--------
+git gui blame Makefile::
+
+ Show the contents of the file 'Makefile' in the current
+ working directory, and provide annotations for both the
+ original author of each line, and who moved the line to its
+ current location. The uncommitted file is annotated, and
+ uncommitted changes (if any) are explicitly attributed to
+ 'Not Yet Committed'.
+
+git gui blame v0.99.8 Makefile::
+
+ Show the contents of 'Makefile' in revision 'v0.99.8'
+ and provide annotations for each line. Unlike the above
+ example the file is read from the object database and not
+ the working directory.
+
+git gui citool::
+
+ Make one commit and return to the shell when it is complete.
+
+git citool::
+
+ Same as 'git gui citool' (above).
+
+git gui browser maint::
+
+ Show a browser for the tree of the 'maint' branch. Files
+ selected in the browser can be viewed with the internal
+ blame viewer.
+
+See Also
+--------
+'gitk(1)'::
+ The git repository browser. Shows branches, commit history
+ and file differences. gitk is the utility started by
+ git-gui's Repository Visualize actions.
+
+Other
+-----
+git-gui is actually maintained as an independent project, but stable
+versions are distributed as part of the Git suite for the convience
+of end users.
+
+A git-gui development repository can be obtained from:
+
+ git clone git://repo.or.cz/git-gui.git
+
+or
+
+ git clone http://repo.or.cz/r/git-gui.git
+
+or browsed online at http://repo.or.cz/w/git-gui.git/[].
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
git-am.sh \
git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
git-merge-resolve.sh git-merge-ours.sh \
- git-lost-found.sh git-quiltimport.sh git-submodule.sh
+ git-lost-found.sh git-quiltimport.sh git-submodule.sh \
+ git-filter-branch.sh
SCRIPT_PERL = \
git-add--interactive.perl \
dir->nr = dst - dir->entries;
for (i = 0; i < specs; i++) {
- struct stat st;
- const char *match;
- if (seen[i])
- continue;
-
- match = pathspec[i];
- if (!match[0])
- continue;
-
- /* Existing file? We must have ignored it */
- if (!lstat(match, &st)) {
- struct dir_entry *ent;
-
- ent = dir_add_name(dir, match, strlen(match));
- ent->ignored = 1;
- if (S_ISDIR(st.st_mode))
- ent->ignored_dir = 1;
- continue;
- }
- die("pathspec '%s' did not match any files", match);
+ if (!seen[i] && !file_exists(pathspec[i]))
+ die("pathspec '%s' did not match any files",
+ pathspec[i]);
}
}
-static void fill_directory(struct dir_struct *dir, const char **pathspec)
+static void fill_directory(struct dir_struct *dir, const char **pathspec,
+ int ignored_too)
{
const char *path, *base;
int baselen;
/* Set up the default git porcelain excludes */
memset(dir, 0, sizeof(*dir));
- dir->exclude_per_dir = ".gitignore";
- path = git_path("info/exclude");
- if (!access(path, R_OK))
- add_excludes_from_file(dir, path);
- if (!access(excludes_file, R_OK))
- add_excludes_from_file(dir, excludes_file);
+ if (!ignored_too) {
+ dir->collect_ignored = 1;
+ dir->exclude_per_dir = ".gitignore";
+ path = git_path("info/exclude");
+ if (!access(path, R_OK))
+ add_excludes_from_file(dir, path);
+ if (!access(excludes_file, R_OK))
+ add_excludes_from_file(dir, excludes_file);
+ }
/*
* Calculate common prefix for the pathspec, and
}
pathspec = get_pathspec(prefix, argv + i);
- fill_directory(&dir, pathspec);
+ fill_directory(&dir, pathspec, ignored_too);
if (show_only) {
const char *sep = "", *eof = "";
for (i = 0; i < dir.nr; i++) {
- if (!ignored_too && dir.entries[i]->ignored)
- continue;
printf("%s%s", sep, dir.entries[i]->name);
sep = " ";
eof = "\n";
if (read_cache() < 0)
die("index file corrupt");
- if (!ignored_too) {
- int has_ignored = 0;
- for (i = 0; i < dir.nr; i++)
- if (dir.entries[i]->ignored)
- has_ignored = 1;
- if (has_ignored) {
- fprintf(stderr, ignore_warning);
- for (i = 0; i < dir.nr; i++) {
- if (!dir.entries[i]->ignored)
- continue;
- fprintf(stderr, "%s", dir.entries[i]->name);
- if (dir.entries[i]->ignored_dir)
- fprintf(stderr, " (directory)");
- fputc('\n', stderr);
- }
- fprintf(stderr,
- "Use -f if you really want to add them.\n");
- exit(1);
+ if (dir.ignored_nr) {
+ fprintf(stderr, ignore_warning);
+ for (i = 0; i < dir.ignored_nr; i++) {
+ fprintf(stderr, "%s\n", dir.ignored[i]->name);
}
+ fprintf(stderr, "Use -f if you really want to add them.\n");
+ exit(1);
}
for (i = 0; i < dir.nr; i++)
char c;
int color;
struct commit *commit;
- char subject[256];
switch (item->kind) {
case REF_LOCAL_BRANCH:
}
if (verbose) {
+ char *subject = NULL;
+ unsigned long subject_len = 0;
+ const char *sub = " **** invalid ref ****";
+
commit = lookup_commit(item->sha1);
- if (commit && !parse_commit(commit))
+ if (commit && !parse_commit(commit)) {
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- subject, sizeof(subject), 0,
+ &subject, &subject_len, 0,
NULL, NULL, 0);
- else
- strcpy(subject, " **** invalid ref ****");
+ sub = subject;
+ }
printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color),
maxwidth, item->name,
branch_get_color(COLOR_BRANCH_RESET),
- find_unique_abbrev(item->sha1, abbrev), subject);
+ find_unique_abbrev(item->sha1, abbrev), sub);
+ if (subject)
+ free(subject);
} else {
printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
branch_get_color(COLOR_BRANCH_RESET));
sign = '-';
if (verbose) {
- static char buf[16384];
+ char *buf = NULL;
+ unsigned long buflen = 0;
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- buf, sizeof(buf), 0, NULL, NULL, 0);
+ &buf, &buflen, 0, NULL, NULL, 0);
printf("%c %s %s\n", sign,
sha1_to_hex(commit->object.sha1), buf);
+ free(buf);
}
else {
printf("%c %s\n", sign,
putchar('\n');
if (revs.verbose_header) {
- static char pretty_header[16384];
+ char *buf = NULL;
+ unsigned long buflen = 0;
pretty_print_commit(revs.commit_format, commit, ~0,
- pretty_header, sizeof(pretty_header),
+ &buf, &buflen,
revs.abbrev, NULL, NULL, revs.date_mode);
- printf("%s%c", pretty_header, hdr_termination);
+ printf("%s%c", buf, hdr_termination);
+ free(buf);
}
fflush(stdout);
if (commit->parents) {
static void show_one_commit(struct commit *commit, int no_name)
{
- char pretty[256], *cp;
+ char *pretty = NULL;
+ const char *pretty_str = "(unavailable)";
+ unsigned long pretty_len = 0;
struct commit_name *name = commit->util;
- if (commit->object.parsed)
+
+ if (commit->object.parsed) {
pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
- pretty, sizeof(pretty), 0, NULL, NULL, 0);
- else
- strcpy(pretty, "(unavailable)");
- if (!prefixcmp(pretty, "[PATCH] "))
- cp = pretty + 8;
- else
- cp = pretty;
+ &pretty, &pretty_len,
+ 0, NULL, NULL, 0);
+ pretty_str = pretty;
+ }
+ if (!prefixcmp(pretty_str, "[PATCH] "))
+ pretty_str += 8;
if (!no_name) {
if (name && name->head_name) {
printf("[%s] ",
find_unique_abbrev(commit->object.sha1, 7));
}
- puts(cp);
+ puts(pretty_str);
+ free(pretty);
}
static char *ref_name[MAX_REVS + 1];
if (0 <= it->entry_count) {
if (size < 20)
goto free_return;
- hashcpy(it->sha1, (unsigned char*)buf);
+ hashcpy(it->sha1, (const unsigned char*)buf);
buf += 20;
size -= 20;
}
*/
#define ALLOC_GROW(x, nr, alloc) \
do { \
- if ((nr) >= alloc) { \
- alloc = alloc_nr(alloc); \
+ if ((nr) > alloc) { \
+ if (alloc_nr(alloc) < (nr)) \
+ alloc = (nr); \
+ else \
+ alloc = alloc_nr(alloc); \
x = xrealloc((x), alloc * sizeof(*(x))); \
} \
} while(0)
return bp - buf;
}
+static unsigned long bound_rfc2047(unsigned long len, const char *encoding)
+{
+ /* upper bound of q encoded string of length 'len' */
+ unsigned long elen = strlen(encoding);
+
+ return len * 3 + elen + 100;
+}
+
static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
const char *line, enum date_mode dmode,
const char *encoding)
}
static long format_commit_message(const struct commit *commit,
- const char *msg, char *buf, unsigned long space)
+ const char *msg, char **buf_p, unsigned long *space_p)
{
struct interp table[] = {
{ "%H" }, /* commit hash */
if (!table[i].value)
interp_set_entry(table, i, "<unknown>");
- interpolate(buf, space, user_format, table, ARRAY_SIZE(table));
+ do {
+ char *buf = *buf_p;
+ unsigned long space = *space_p;
+
+ space = interpolate(buf, space, user_format,
+ table, ARRAY_SIZE(table));
+ if (!space)
+ break;
+ buf = xrealloc(buf, space);
+ *buf_p = buf;
+ *space_p = space;
+ } while (1);
interp_clear_table(table, ARRAY_SIZE(table));
- return strlen(buf);
+ return strlen(*buf_p);
+}
+
+static void pp_header(enum cmit_fmt fmt,
+ int abbrev,
+ enum date_mode dmode,
+ const char *encoding,
+ const struct commit *commit,
+ const char **msg_p,
+ unsigned long *len_p,
+ unsigned long *ofs_p,
+ char **buf_p,
+ unsigned long *space_p)
+{
+ int parents_shown = 0;
+
+ for (;;) {
+ const char *line = *msg_p;
+ char *dst;
+ int linelen = get_one_line(*msg_p, *len_p);
+ unsigned long len;
+
+ if (!linelen)
+ return;
+ *msg_p += linelen;
+ *len_p -= linelen;
+
+ if (linelen == 1)
+ /* End of header */
+ return;
+
+ ALLOC_GROW(*buf_p, linelen + *ofs_p + 20, *space_p);
+ dst = *buf_p + *ofs_p;
+
+ if (fmt == CMIT_FMT_RAW) {
+ memcpy(dst, line, linelen);
+ *ofs_p += linelen;
+ continue;
+ }
+
+ if (!memcmp(line, "parent ", 7)) {
+ if (linelen != 48)
+ die("bad parent line in commit");
+ continue;
+ }
+
+ if (!parents_shown) {
+ struct commit_list *parent;
+ int num;
+ for (parent = commit->parents, num = 0;
+ parent;
+ parent = parent->next, num++)
+ ;
+ /* with enough slop */
+ num = *ofs_p + num * 50 + 20;
+ ALLOC_GROW(*buf_p, num, *space_p);
+ dst = *buf_p + *ofs_p;
+ *ofs_p += add_merge_info(fmt, dst, commit, abbrev);
+ parents_shown = 1;
+ }
+
+ /*
+ * MEDIUM == DEFAULT shows only author with dates.
+ * FULL shows both authors but not dates.
+ * FULLER shows both authors and dates.
+ */
+ if (!memcmp(line, "author ", 7)) {
+ len = linelen;
+ if (fmt == CMIT_FMT_EMAIL)
+ len = bound_rfc2047(linelen, encoding);
+ ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
+ dst = *buf_p + *ofs_p;
+ *ofs_p += add_user_info("Author", fmt, dst,
+ line + 7, dmode, encoding);
+ }
+
+ if (!memcmp(line, "committer ", 10) &&
+ (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+ len = linelen;
+ if (fmt == CMIT_FMT_EMAIL)
+ len = bound_rfc2047(linelen, encoding);
+ ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
+ dst = *buf_p + *ofs_p;
+ *ofs_p += add_user_info("Commit", fmt, dst,
+ line + 10, dmode, encoding);
+ }
+ }
+}
+
+static void pp_title_line(enum cmit_fmt fmt,
+ const char **msg_p,
+ unsigned long *len_p,
+ unsigned long *ofs_p,
+ char **buf_p,
+ unsigned long *space_p,
+ int indent,
+ const char *subject,
+ const char *after_subject,
+ const char *encoding,
+ int plain_non_ascii)
+{
+ char *title;
+ unsigned long title_alloc, title_len;
+ unsigned long len;
+
+ title_len = 0;
+ title_alloc = 80;
+ title = xmalloc(title_alloc);
+ for (;;) {
+ const char *line = *msg_p;
+ int linelen = get_one_line(line, *len_p);
+ *msg_p += linelen;
+ *len_p -= linelen;
+
+ if (!linelen || is_empty_line(line, &linelen))
+ break;
+
+ if (title_alloc <= title_len + linelen + 2) {
+ title_alloc = title_len + linelen + 80;
+ title = xrealloc(title, title_alloc);
+ }
+ len = 0;
+ if (title_len) {
+ if (fmt == CMIT_FMT_EMAIL) {
+ len++;
+ title[title_len++] = '\n';
+ }
+ len++;
+ title[title_len++] = ' ';
+ }
+ memcpy(title + title_len, line, linelen);
+ title_len += linelen;
+ }
+
+ /* Enough slop for the MIME header and rfc2047 */
+ len = bound_rfc2047(title_len, encoding)+ 1000;
+ if (subject)
+ len += strlen(subject);
+ if (after_subject)
+ len += strlen(after_subject);
+ if (encoding)
+ len += strlen(encoding);
+ ALLOC_GROW(*buf_p, title_len + *ofs_p + len, *space_p);
+
+ if (subject) {
+ len = strlen(subject);
+ memcpy(*buf_p + *ofs_p, subject, len);
+ *ofs_p += len;
+ *ofs_p += add_rfc2047(*buf_p + *ofs_p,
+ title, title_len, encoding);
+ } else {
+ memcpy(*buf_p + *ofs_p, title, title_len);
+ *ofs_p += title_len;
+ }
+ (*buf_p)[(*ofs_p)++] = '\n';
+ if (plain_non_ascii) {
+ const char *header_fmt =
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=%s\n"
+ "Content-Transfer-Encoding: 8bit\n";
+ *ofs_p += snprintf(*buf_p + *ofs_p,
+ *space_p - *ofs_p,
+ header_fmt, encoding);
+ }
+ if (after_subject) {
+ len = strlen(after_subject);
+ memcpy(*buf_p + *ofs_p, after_subject, len);
+ *ofs_p += len;
+ }
+ free(title);
+ if (fmt == CMIT_FMT_EMAIL) {
+ ALLOC_GROW(*buf_p, *ofs_p + 20, *space_p);
+ (*buf_p)[(*ofs_p)++] = '\n';
+ }
+}
+
+static void pp_remainder(enum cmit_fmt fmt,
+ const char **msg_p,
+ unsigned long *len_p,
+ unsigned long *ofs_p,
+ char **buf_p,
+ unsigned long *space_p,
+ int indent)
+{
+ int first = 1;
+ for (;;) {
+ const char *line = *msg_p;
+ int linelen = get_one_line(line, *len_p);
+ *msg_p += linelen;
+ *len_p -= linelen;
+
+ if (!linelen)
+ break;
+
+ if (is_empty_line(line, &linelen)) {
+ if (first)
+ continue;
+ if (fmt == CMIT_FMT_SHORT)
+ break;
+ }
+ first = 0;
+
+ ALLOC_GROW(*buf_p, *ofs_p + linelen + indent + 20, *space_p);
+ if (indent) {
+ memset(*buf_p + *ofs_p, ' ', indent);
+ *ofs_p += indent;
+ }
+ memcpy(*buf_p + *ofs_p, line, linelen);
+ *ofs_p += linelen;
+ (*buf_p)[(*ofs_p)++] = '\n';
+ }
}
unsigned long pretty_print_commit(enum cmit_fmt fmt,
const struct commit *commit,
unsigned long len,
- char *buf, unsigned long space,
+ char **buf_p, unsigned long *space_p,
int abbrev, const char *subject,
const char *after_subject,
enum date_mode dmode)
{
- int hdr = 1, body = 0, seen_title = 0;
unsigned long offset = 0;
+ unsigned long beginning_of_body;
int indent = 4;
- int parents_shown = 0;
const char *msg = commit->buffer;
int plain_non_ascii = 0;
char *reencoded;
const char *encoding;
+ char *buf;
if (fmt == CMIT_FMT_USERFORMAT)
- return format_commit_message(commit, msg, buf, space);
+ return format_commit_message(commit, msg, buf_p, space_p);
encoding = (git_log_output_encoding
? git_log_output_encoding
if (!encoding)
encoding = "utf-8";
reencoded = logmsg_reencode(commit, encoding);
- if (reencoded)
+ if (reencoded) {
msg = reencoded;
+ len = strlen(reencoded);
+ }
if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
indent = 0;
}
}
+ pp_header(fmt, abbrev, dmode, encoding,
+ commit, &msg, &len,
+ &offset, buf_p, space_p);
+ if (fmt != CMIT_FMT_ONELINE && !subject) {
+ ALLOC_GROW(*buf_p, offset + 20, *space_p);
+ (*buf_p)[offset++] = '\n';
+ }
+
+ /* Skip excess blank lines at the beginning of body, if any... */
for (;;) {
- const char *line = msg;
int linelen = get_one_line(msg, len);
-
+ int ll = linelen;
if (!linelen)
break;
-
- /*
- * We want some slop for indentation and a possible
- * final "...". Thus the "+ 20".
- */
- if (offset + linelen + 20 > space) {
- memcpy(buf + offset, " ...\n", 8);
- offset += 8;
+ if (!is_empty_line(msg, &ll))
break;
- }
-
msg += linelen;
len -= linelen;
- if (hdr) {
- if (linelen == 1) {
- hdr = 0;
- if ((fmt != CMIT_FMT_ONELINE) && !subject)
- buf[offset++] = '\n';
- continue;
- }
- if (fmt == CMIT_FMT_RAW) {
- memcpy(buf + offset, line, linelen);
- offset += linelen;
- continue;
- }
- if (!memcmp(line, "parent ", 7)) {
- if (linelen != 48)
- die("bad parent line in commit");
- continue;
- }
-
- if (!parents_shown) {
- offset += add_merge_info(fmt, buf + offset,
- commit, abbrev);
- parents_shown = 1;
- continue;
- }
- /*
- * MEDIUM == DEFAULT shows only author with dates.
- * FULL shows both authors but not dates.
- * FULLER shows both authors and dates.
- */
- if (!memcmp(line, "author ", 7))
- offset += add_user_info("Author", fmt,
- buf + offset,
- line + 7,
- dmode,
- encoding);
- if (!memcmp(line, "committer ", 10) &&
- (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
- offset += add_user_info("Commit", fmt,
- buf + offset,
- line + 10,
- dmode,
- encoding);
- continue;
- }
+ }
- if (!subject)
- body = 1;
+ /* These formats treat the title line specially. */
+ if (fmt == CMIT_FMT_ONELINE
+ || fmt == CMIT_FMT_EMAIL)
+ pp_title_line(fmt, &msg, &len, &offset,
+ buf_p, space_p, indent,
+ subject, after_subject, encoding,
+ plain_non_ascii);
- if (is_empty_line(line, &linelen)) {
- if (!seen_title)
- continue;
- if (!body)
- continue;
- if (subject)
- continue;
- if (fmt == CMIT_FMT_SHORT)
- break;
- }
+ beginning_of_body = offset;
+ if (fmt != CMIT_FMT_ONELINE)
+ pp_remainder(fmt, &msg, &len, &offset,
+ buf_p, space_p, indent);
- seen_title = 1;
- if (subject) {
- int slen = strlen(subject);
- memcpy(buf + offset, subject, slen);
- offset += slen;
- offset += add_rfc2047(buf + offset, line, linelen,
- encoding);
- }
- else {
- memset(buf + offset, ' ', indent);
- memcpy(buf + offset + indent, line, linelen);
- offset += linelen + indent;
- }
- buf[offset++] = '\n';
- if (fmt == CMIT_FMT_ONELINE)
- break;
- if (subject && plain_non_ascii) {
- int sz;
- char header[512];
- const char *header_fmt =
- "MIME-Version: 1.0\n"
- "Content-Type: text/plain; charset=%s\n"
- "Content-Transfer-Encoding: 8bit\n";
- sz = snprintf(header, sizeof(header), header_fmt,
- encoding);
- if (sizeof(header) < sz)
- die("Encoding name %s too long", encoding);
- memcpy(buf + offset, header, sz);
- offset += sz;
- }
- if (after_subject) {
- int slen = strlen(after_subject);
- if (slen > space - offset - 1)
- slen = space - offset - 1;
- memcpy(buf + offset, after_subject, slen);
- offset += slen;
- after_subject = NULL;
- }
- subject = NULL;
- }
- while (offset && isspace(buf[offset-1]))
+ while (offset && isspace((*buf_p)[offset-1]))
offset--;
+
+ ALLOC_GROW(*buf_p, offset + 20, *space_p);
+ buf = *buf_p;
+
/* Make sure there is an EOLN for the non-oneline case */
if (fmt != CMIT_FMT_ONELINE)
buf[offset++] = '\n';
+
/*
- * make sure there is another EOLN to separate the headers from whatever
- * body the caller appends if we haven't already written a body
+ * The caller may append additional body text in e-mail
+ * format. Make sure we did not strip the blank line
+ * between the header and the body.
*/
- if (fmt == CMIT_FMT_EMAIL && !body)
+ if (fmt == CMIT_FMT_EMAIL && offset <= beginning_of_body)
buf[offset++] = '\n';
buf[offset] = '\0';
-
free(reencoded);
return offset;
}
};
extern enum cmit_fmt get_commit_format(const char *arg);
-extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
/** Removes the first commit from a list sorted by date, and adds all
* of its parents.
return 0;
}
+static int basename_same(struct diff_filespec *src, struct diff_filespec *dst)
+{
+ int src_len = strlen(src->path), dst_len = strlen(dst->path);
+ while (src_len && dst_len) {
+ char c1 = src->path[--src_len];
+ char c2 = dst->path[--dst_len];
+ if (c1 != c2)
+ return 0;
+ if (c1 == '/')
+ return 1;
+ }
+ return (!src_len || src->path[src_len - 1] == '/') &&
+ (!dst_len || dst->path[dst_len - 1] == '/');
+}
+
struct diff_score {
int src; /* index in rename_src */
int dst; /* index in rename_dst */
*/
if (!dst->size)
score = 0; /* should not happen */
- else
+ else {
score = (int)(src_copied * MAX_SCORE / max_size);
+ if (basename_same(src, dst))
+ score++;
+ }
return score;
}
if (rename_dst[i].pair)
continue; /* dealt with an earlier round */
for (j = 0; j < rename_src_nr; j++) {
+ int k;
struct diff_filespec *one = rename_src[j].one;
if (!is_exact_match(one, two, contents_too))
continue;
+
+ /* see if there is a basename match, too */
+ for (k = j; k < rename_src_nr; k++) {
+ one = rename_src[k].one;
+ if (basename_same(one, two) &&
+ is_exact_match(one, two,
+ contents_too)) {
+ j = k;
+ break;
+ }
+ }
+
record_rename_pair(i, j, (int)MAX_SCORE);
rename_count++;
break; /* we are done with this entry */
struct dir_entry *ent;
ent = xmalloc(sizeof(*ent) + len + 1);
- ent->ignored = ent->ignored_dir = 0;
ent->len = len;
memcpy(ent->name, pathname, len);
ent->name[len] = 0;
if (cache_name_pos(pathname, len) >= 0)
return NULL;
- ALLOC_GROW(dir->entries, dir->nr, dir->alloc);
+ ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
}
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+{
+ if (cache_name_pos(pathname, len) >= 0)
+ return NULL;
+
+ ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
+ return dir->ignored[dir->ignored_nr++] = dir_entry_new(pathname, len);
+}
+
enum exist_status {
index_nonexistent = 0,
index_directory,
return 0;
}
+static int in_pathspec(const char *path, int len, const struct path_simplify *simplify)
+{
+ if (simplify) {
+ for (; simplify->path; simplify++) {
+ if (len == simplify->len
+ && !memcmp(path, simplify->path, len))
+ return 1;
+ }
+ }
+ return 0;
+}
+
/*
* Read a directory tree. We currently ignore anything but
* directories, regular files and symlinks. That's because git
continue;
exclude = excluded(dir, fullname);
+ if (exclude && dir->collect_ignored
+ && in_pathspec(fullname, baselen + len, simplify))
+ dir_add_ignored(dir, fullname, baselen + len);
if (exclude != dir->show_ignored) {
if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
continue;
read_directory_recursive(dir, path, base, baselen, 0, simplify);
free_simplify(simplify);
qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+ qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
return dir->nr;
}
struct dir_entry {
- unsigned int ignored : 1;
- unsigned int ignored_dir : 1;
- unsigned int len : 30;
+ unsigned int len;
char name[FLEX_ARRAY]; /* more */
};
struct dir_struct {
int nr, alloc;
+ int ignored_nr, ignored_alloc;
unsigned int show_ignored:1,
show_other_directories:1,
hide_empty_directories:1,
- no_gitlinks:1;
+ no_gitlinks:1,
+ collect_ignored:1;
struct dir_entry **entries;
+ struct dir_entry **ignored;
/* Exclude info */
const char *exclude_per_dir;
)
)
+ # Upstream URL
+ git-config remote."$origin".url "$repo" &&
+
+ # Set up the mappings to track the remote branches.
+ git-config remote."$origin".fetch \
+ "+refs/heads/*:$remote_top/*" '^$' &&
+
# Write out remote.$origin config, and update our "$head_points_at".
case "$head_points_at" in
?*)
git-symbolic-ref HEAD "refs/heads/$head_points_at" &&
# Tracking branch for the primary branch at the remote.
- origin_track="$remote_top/$head_points_at" &&
git-update-ref HEAD "$head_sha1" &&
- # Upstream URL
- git-config remote."$origin".url "$repo" &&
-
- # Set up the mappings to track the remote branches.
- git-config remote."$origin".fetch \
- "+refs/heads/*:$remote_top/*" '^$' &&
rm -f "refs/remotes/$origin/HEAD"
git-symbolic-ref "refs/remotes/$origin/HEAD" \
"refs/remotes/$origin/$head_points_at" &&
git-config branch."$head_points_at".remote "$origin" &&
git-config branch."$head_points_at".merge "refs/heads/$head_points_at"
+ ;;
+ '')
+ # Source had detached HEAD pointing nowhere
+ git-update-ref --no-deref HEAD "$head_sha1" &&
+ rm -f "refs/remotes/$origin/HEAD"
+ ;;
esac
case "$no_checkout" in
or die "Cannot write branch $branch for update: $!\n";
if ($tag) {
- my ($in, $out) = ('','');
my ($xtag) = $tag;
$xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
$xtag =~ tr/_/\./ if ( $opt_u );
--- /dev/null
+#!/bin/sh
+#
+# Rewrite revision history
+# Copyright (c) Petr Baudis, 2006
+# Minimal changes to "port" it to core-git (c) Johannes Schindelin, 2007
+#
+# Lets you rewrite GIT revision history by creating a new branch from
+# your current branch by applying custom filters on each revision.
+# Those filters can modify each tree (e.g. removing a file or running
+# a perl rewrite on all files) or information about each commit.
+# Otherwise, all information (including original commit times or merge
+# information) will be preserved.
+#
+# The command takes the new branch name as a mandatory argument and
+# the filters as optional arguments. If you specify no filters, the
+# commits will be recommitted without any changes, which would normally
+# have no effect and result with the new branch pointing to the same
+# branch as your current branch. (Nevertheless, this may be useful in
+# the future for compensating for some Git bugs or such, therefore
+# such a usage is permitted.)
+#
+# WARNING! The rewritten history will have different ids for all the
+# objects and will not converge with the original branch. You will not
+# be able to easily push and distribute the rewritten branch. Please do
+# not use this command if you do not know the full implications, and
+# avoid using it anyway - do not do what a simple single commit on top
+# of the current version would fix.
+#
+# Always verify that the rewritten version is correct before disposing
+# the original branch.
+#
+# Note that since this operation is extensively I/O expensive, it might
+# be a good idea to do it off-disk, e.g. on tmpfs. Reportedly the speedup
+# is very noticeable.
+#
+# OPTIONS
+# -------
+# -d TEMPDIR:: The path to the temporary tree used for rewriting
+# When applying a tree filter, the command needs to temporary
+# checkout the tree to some directory, which may consume
+# considerable space in case of large projects. By default it
+# does this in the '.git-rewrite/' directory but you can override
+# that choice by this parameter.
+#
+# Filters
+# ~~~~~~~
+# The filters are applied in the order as listed below. The COMMAND
+# argument is always evaluated in shell using the 'eval' command.
+# The $GIT_COMMIT environment variable is permanently set to contain
+# the id of the commit being rewritten. The author/committer environment
+# variables are set before the first filter is run.
+#
+# A 'map' function is available that takes an "original sha1 id" argument
+# and outputs a "rewritten sha1 id" if the commit has been already
+# rewritten, fails otherwise; the 'map' function can return several
+# ids on separate lines if your commit filter emitted multiple commits
+# (see below).
+#
+# --env-filter COMMAND:: The filter for modifying environment
+# This is the filter for modifying the environment in which
+# the commit will be performed. Specifically, you might want
+# to rewrite the author/committer name/email/time environment
+# variables (see `git-commit` for details). Do not forget to
+# re-export the variables.
+#
+# --tree-filter COMMAND:: The filter for rewriting tree (and its contents)
+# This is the filter for rewriting the tree and its contents.
+# The COMMAND argument is evaluated in shell with the working
+# directory set to the root of the checked out tree. The new tree
+# is then used as-is (new files are auto-added, disappeared files
+# are auto-removed - .gitignore files nor any other ignore rules
+# HAVE NO EFFECT!).
+#
+# --index-filter COMMAND:: The filter for rewriting index
+# This is the filter for rewriting the Git's directory index.
+# It is similar to the tree filter but does not check out the
+# tree, which makes it much faster. However, you must use the
+# lowlevel Git index manipulation commands to do your work.
+#
+# --parent-filter COMMAND:: The filter for rewriting parents
+# This is the filter for rewriting the commit's parent list.
+# It will receive the parent string on stdin and shall output
+# the new parent string on stdout. The parent string is in
+# format accepted by `git-commit-tree`: empty for initial
+# commit, "-p parent" for a normal commit and "-p parent1
+# -p parent2 -p parent3 ..." for a merge commit.
+#
+# --msg-filter COMMAND:: The filter for rewriting commit message
+# This is the filter for rewriting the commit messages.
+# The COMMAND argument is evaluated in shell with the original
+# commit message on standard input; its standard output is
+# is used as the new commit message.
+#
+# --commit-filter COMMAND:: The filter for performing the commit
+# If this filter is passed, it will be called instead of the
+# `git-commit-tree` command, with those arguments:
+#
+# TREE_ID [-p PARENT_COMMIT_ID]...
+#
+# and the log message on stdin. The commit id is expected on
+# stdout. As a special extension, the commit filter may emit
+# multiple commit ids; in that case, all of them will be used
+# as parents instead of the original commit in further commits.
+#
+# --tag-name-filter COMMAND:: The filter for rewriting tag names.
+# If this filter is passed, it will be called for every tag ref
+# that points to a rewritten object (or to a tag object which
+# points to a rewritten object). The original tag name is passed
+# via standard input, and the new tag name is expected on standard
+# output.
+#
+# The original tags are not deleted, but can be overwritten;
+# use "--tag-name-filter=cat" to simply update the tags. In this
+# case, be very careful and make sure you have the old tags
+# backed up in case the conversion has run afoul.
+#
+# Note that there is currently no support for proper rewriting of
+# tag objects; in layman terms, if the tag has a message or signature
+# attached, the rewritten tag won't have it. Sorry. (It is by
+# definition impossible to preserve signatures at any rate, though.)
+#
+# --subdirectory-filter DIRECTORY:: Only regard the history, as seen by
+# the given subdirectory. The result will contain that directory as
+# its project root.
+#
+# EXAMPLE USAGE
+# -------------
+# Suppose you want to remove a file (containing confidential information
+# or copyright violation) from all commits:
+#
+# git-filter-branch --tree-filter 'rm filename' newbranch
+#
+# A significantly faster version:
+#
+# git-filter-branch --index-filter 'git-update-index --remove filename' newbranch
+#
+# Now, you will get the rewritten history saved in the branch 'newbranch'
+# (your current branch is left untouched).
+#
+# To "etch-graft" a commit to the revision history (set a commit to be
+# the parent of the current initial commit and propagate that):
+#
+# git-filter-branch --parent-filter sed\ 's/^$/-p graftcommitid/' newbranch
+#
+# (if the parent string is empty - therefore we are dealing with the
+# initial commit - add graftcommit as a parent). Note that this assumes
+# history with a single root (that is, no git-merge without common ancestors
+# happened). If this is not the case, use:
+#
+# git-filter-branch --parent-filter 'cat; [ "$GIT_COMMIT" = "COMMIT" ] && echo "-p GRAFTCOMMIT"' newbranch
+#
+# To remove commits authored by "Darl McBribe" from the history:
+#
+# git-filter-branch --commit-filter 'if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ]; then shift; while [ -n "$1" ]; do shift; echo "$1"; shift; done; else git-commit-tree "$@"; fi' newbranch
+#
+# (the shift magic first throws away the tree id and then the -p
+# parameters). Note that this handles merges properly! In case Darl
+# committed a merge between P1 and P2, it will be propagated properly
+# and all children of the merge will become merge commits with P1,P2
+# as their parents instead of the merge commit.
+#
+# To restrict rewriting to only part of the history, specify a revision
+# range in addition to the new branch name. The new branch name will
+# point to the top-most revision that a 'git rev-list' of this range
+# will print.
+#
+# Consider this history:
+#
+# D--E--F--G--H
+# / /
+# A--B-----C
+#
+# To rewrite commits D,E,F,G,H, use:
+#
+# git-filter-branch ... new-H C..H
+#
+# To rewrite commits E,F,G,H, use one of these:
+#
+# git-filter-branch ... new-H C..H --not D
+# git-filter-branch ... new-H D..H --not C
+#
+# To move the whole tree into a subdirectory, or remove it from there:
+#
+# git-filter-branch --index-filter \
+# 'git-ls-files -s | sed "s-\t-&newsubdir/-" |
+# GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
+# git-update-index --index-info &&
+# mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' directorymoved
+
+# Testsuite: TODO
+
+set -e
+
+USAGE="git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
+. git-sh-setup
+
+map()
+{
+ # if it was not rewritten, take the original
+ test -r "$workdir/../map/$1" || echo "$1"
+ cat "$workdir/../map/$1"
+}
+
+# When piped a commit, output a script to set the ident of either
+# "author" or "committer
+
+set_ident () {
+ lid="$(echo "$1" | tr "A-Z" "a-z")"
+ uid="$(echo "$1" | tr "a-z" "A-Z")"
+ pick_id_script='
+ /^'$lid' /{
+ s/'\''/'\''\\'\'\''/g
+ h
+ s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/export GIT_'$uid'_NAME='\''&'\''/p
+
+ g
+ s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
+
+ g
+ s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/export GIT_'$uid'_DATE='\''&'\''/p
+
+ q
+ }
+ '
+
+ LANG=C LC_ALL=C sed -ne "$pick_id_script"
+ # Ensure non-empty id name.
+ echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
+}
+
+tempdir=.git-rewrite
+filter_env=
+filter_tree=
+filter_index=
+filter_parent=
+filter_msg=cat
+filter_commit='git-commit-tree "$@"'
+filter_tag_name=
+filter_subdir=
+while case "$#" in 0) usage;; esac
+do
+ case "$1" in
+ --)
+ shift
+ break
+ ;;
+ -*)
+ ;;
+ *)
+ break;
+ esac
+
+ # all switches take one argument
+ ARG="$1"
+ case "$#" in 1) usage ;; esac
+ shift
+ OPTARG="$1"
+ shift
+
+ case "$ARG" in
+ -d)
+ tempdir="$OPTARG"
+ ;;
+ --env-filter)
+ filter_env="$OPTARG"
+ ;;
+ --tree-filter)
+ filter_tree="$OPTARG"
+ ;;
+ --index-filter)
+ filter_index="$OPTARG"
+ ;;
+ --parent-filter)
+ filter_parent="$OPTARG"
+ ;;
+ --msg-filter)
+ filter_msg="$OPTARG"
+ ;;
+ --commit-filter)
+ filter_commit="$OPTARG"
+ ;;
+ --tag-name-filter)
+ filter_tag_name="$OPTARG"
+ ;;
+ --subdirectory-filter)
+ filter_subdir="$OPTARG"
+ ;;
+ *)
+ usage
+ ;;
+ esac
+done
+
+dstbranch="$1"
+shift
+test -n "$dstbranch" || die "missing branch name"
+git-show-ref "refs/heads/$dstbranch" 2> /dev/null &&
+ die "branch $dstbranch already exists"
+
+test ! -e "$tempdir" || die "$tempdir already exists, please remove it"
+mkdir -p "$tempdir/t"
+cd "$tempdir/t"
+workdir="$(pwd)"
+
+case "$GIT_DIR" in
+/*)
+ ;;
+*)
+ export GIT_DIR="$(pwd)/../../$GIT_DIR"
+ ;;
+esac
+
+export GIT_INDEX_FILE="$(pwd)/../index"
+git-read-tree # seed the index file
+
+ret=0
+
+
+mkdir ../map # map old->new commit ids for rewriting parents
+
+case "$filter_subdir" in
+"")
+ git-rev-list --reverse --topo-order --default HEAD \
+ --parents "$@"
+ ;;
+*)
+ git-rev-list --reverse --topo-order --default HEAD \
+ --parents --full-history "$@" -- "$filter_subdir"
+esac > ../revs
+commits=$(cat ../revs | wc -l | tr -d " ")
+
+test $commits -eq 0 && die "Found nothing to rewrite"
+
+i=0
+while read commit parents; do
+ i=$(($i+1))
+ printf "$commit ($i/$commits) "
+
+ case "$filter_subdir" in
+ "")
+ git-read-tree -i -m $commit
+ ;;
+ *)
+ git-read-tree -i -m $commit:"$filter_subdir"
+ esac
+
+ export GIT_COMMIT=$commit
+ git-cat-file commit "$commit" >../commit
+
+ eval "$(set_ident AUTHOR <../commit)"
+ eval "$(set_ident COMMITTER <../commit)"
+ eval "$filter_env" < /dev/null
+
+ if [ "$filter_tree" ]; then
+ git-checkout-index -f -u -a
+ # files that $commit removed are now still in the working tree;
+ # remove them, else they would be added again
+ git-ls-files -z --others | xargs -0 rm -f
+ eval "$filter_tree" < /dev/null
+ git-diff-index -r $commit | cut -f 2- | tr '\n' '\0' | \
+ xargs -0 git-update-index --add --replace --remove
+ git-ls-files -z --others | \
+ xargs -0 git-update-index --add --replace --remove
+ fi
+
+ eval "$filter_index" < /dev/null
+
+ parentstr=
+ for parent in $parents; do
+ for reparent in $(map "$parent"); do
+ parentstr="$parentstr -p $reparent"
+ done
+ done
+ if [ "$filter_parent" ]; then
+ parentstr="$(echo "$parentstr" | eval "$filter_parent")"
+ fi
+
+ sed -e '1,/^$/d' <../commit | \
+ eval "$filter_msg" | \
+ sh -c "$filter_commit" git-commit-tree $(git-write-tree) $parentstr | \
+ tee ../map/$commit
+done <../revs
+
+src_head=$(tail -n 1 ../revs | sed -e 's/ .*//')
+target_head=$(head -n 1 ../map/$src_head)
+case "$target_head" in
+'')
+ echo Nothing rewritten
+ ;;
+*)
+ git-update-ref refs/heads/"$dstbranch" $target_head
+ if [ $(cat ../map/$src_head | wc -l) -gt 1 ]; then
+ echo "WARNING: Your commit filter caused the head commit to expand to several rewritten commits. Only the first such commit was recorded as the current $dstbranch head but you will need to resolve the situation now (probably by manually merging the other commits). These are all the commits:" >&2
+ sed 's/^/ /' ../map/$src_head >&2
+ ret=1
+ fi
+ ;;
+esac
+
+if [ "$filter_tag_name" ]; then
+ git-for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
+ while read sha1 type ref; do
+ ref="${ref#refs/tags/}"
+ # XXX: Rewrite tagged trees as well?
+ if [ "$type" != "commit" -a "$type" != "tag" ]; then
+ continue;
+ fi
+
+ if [ "$type" = "tag" ]; then
+ # Dereference to a commit
+ sha1t="$sha1"
+ sha1="$(git-rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
+ fi
+
+ [ -f "../map/$sha1" ] || continue
+ new_sha1="$(cat "../map/$sha1")"
+ export GIT_COMMIT="$sha1"
+ new_ref="$(echo "$ref" | eval "$filter_tag_name")"
+
+ echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
+
+ if [ "$type" = "tag" ]; then
+ # Warn that we are not rewriting the tag object itself.
+ warn "unreferencing tag object $sha1t"
+ fi
+
+ git-update-ref "refs/tags/$new_ref" "$new_sha1"
+ done
+fi
+
+cd ../..
+rm -rf "$tempdir"
+echo "Rewritten history saved to the $dstbranch branch"
+
+exit $ret
or die "Cannot write branch $dest for update: $!\n";
}
- if($tag) {
- my($in, $out) = ('','');
+ if ($tag) {
$last_rev = "-" if %$changed_paths;
# the tag was 'complex', i.e. did not refer to a "real" revision
$dest =~ tr/_/\./ if $opt_u;
- $branch = $dest;
-
- my $pid = open2($in, $out, 'git-mktag');
- print $out ("object $cid\n".
- "type commit\n".
- "tag $dest\n".
- "tagger $committer_name <$committer_email> 0 +0000\n") and
- close($out)
- or die "Cannot create tag object $dest: $!\n";
-
- my $tagobj = <$in>;
- chomp $tagobj;
-
- if ( !close($in) or waitpid($pid, 0) != $pid or
- $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
- die "Cannot create tag object $dest: $!\n";
- }
- open(C,">$git_dir/refs/tags/$dest") and
- print C ("$tagobj\n") and
- close(C)
- or die "Cannot create tag $branch: $!\n";
+ system('git-tag', $dest, $cid) == 0
+ or die "Cannot create tag $dest: $!\n";
print "Created tag '$dest' on '$branch'\n" if $opt_v;
}
%{_bindir}/git-gui
%{_bindir}/git-citool
%{_datadir}/git-gui/
-# Not Yet...
-# %{!?_without_docs: %{_mandir}/man1/git-gui.1}
-# %{!?_without_docs: %doc Documentation/git-gui.html}
-# %{!?_without_docs: %{_mandir}/man1/git-citool.1}
-# %{!?_without_docs: %doc Documentation/git-citool.html}
+%{!?_without_docs: %{_mandir}/man1/git-gui.1}
+%{!?_without_docs: %doc Documentation/git-gui.html}
+%{!?_without_docs: %{_mandir}/man1/git-citool.1}
+%{!?_without_docs: %doc Documentation/git-citool.html}
%files -n gitk
%defattr(-,root,root)
%{!?_without_docs: %doc Documentation/technical}
%changelog
+* Thu Jun 21 2007 Shawn O. Pearce <spearce@spearce.org>
+- Added documentation files for git-gui
+
* Tue May 13 2007 Quy Tonthat <qtonthat@gmail.com>
- Added lib files for git-gui
- Added Documentation/technical (As needed by Git Users Manual)
* { "%%", "%"},
* }
*
- * Returns 1 on a successful substitution pass that fits in result,
- * Returns 0 on a failed or overflowing substitution pass.
+ * Returns 0 on a successful substitution pass that fits in result,
+ * Returns a number of bytes needed to hold the full substituted
+ * string otherwise.
*/
-int interpolate(char *result, int reslen,
+unsigned long interpolate(char *result, unsigned long reslen,
const char *orig,
const struct interp *interps, int ninterps)
{
const char *src = orig;
char *dest = result;
- int newlen = 0;
+ unsigned long newlen = 0;
const char *name, *value;
- int namelen, valuelen;
+ unsigned long namelen, valuelen;
int i;
char c;
memset(result, 0, reslen);
- while ((c = *src) && newlen < reslen - 1) {
+ while ((c = *src)) {
if (c == '%') {
/* Try to match an interpolation string. */
for (i = 0; i < ninterps; i++) {
name = interps[i].name;
namelen = strlen(name);
- if (strncmp(src, name, namelen) == 0) {
+ if (strncmp(src, name, namelen) == 0)
break;
- }
}
/* Check for valid interpolation. */
value = interps[i].value;
valuelen = strlen(value);
- if (newlen + valuelen < reslen - 1) {
+ if (newlen + valuelen + 1 < reslen) {
/* Substitute. */
strncpy(dest, value, valuelen);
- newlen += valuelen;
dest += valuelen;
- src += namelen;
- } else {
- /* Something's not fitting. */
- return 0;
}
-
- } else {
- /* Skip bogus interpolation. */
- *dest++ = *src++;
- newlen++;
+ newlen += valuelen;
+ src += namelen;
+ continue;
}
-
- } else {
- /* Straight copy one non-interpolation character. */
- *dest++ = *src++;
- newlen++;
}
+ /* Straight copy one non-interpolation character. */
+ if (newlen + 1 < reslen)
+ *dest++ = *src;
+ src++;
+ newlen++;
}
- return newlen < reslen - 1;
+ if (newlen + 1 < reslen)
+ return 0;
+ else
+ return newlen + 2;
}
extern void interp_set_entry(struct interp *table, int slot, const char *value);
extern void interp_clear_table(struct interp *table, int ninterps);
-extern int interpolate(char *result, int reslen,
- const char *orig,
- const struct interp *interps, int ninterps);
+extern unsigned long interpolate(char *result, unsigned long reslen,
+ const char *orig,
+ const struct interp *interps, int ninterps);
#endif /* INTERPOLATE_H */
return seen_head && seen_name;
}
-static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
+static unsigned long append_signoff(char **buf_p, unsigned long *buf_sz_p,
+ unsigned long at, const char *signoff)
{
static const char signed_off_by[] = "Signed-off-by: ";
- int signoff_len = strlen(signoff);
+ size_t signoff_len = strlen(signoff);
int has_signoff = 0;
- char *cp = buf;
-
- /* Do we have enough space to add it? */
- if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3)
- return at;
+ char *cp;
+ char *buf;
+ unsigned long buf_sz;
+
+ buf = *buf_p;
+ buf_sz = *buf_sz_p;
+ if (buf_sz <= at + strlen(signed_off_by) + signoff_len + 3) {
+ buf_sz += strlen(signed_off_by) + signoff_len + 3;
+ buf = xrealloc(buf, buf_sz);
+ *buf_p = buf;
+ *buf_sz_p = buf_sz;
+ }
+ cp = buf;
/* First see if we already have the sign-off by the signer */
while ((cp = strstr(cp, signed_off_by))) {
void show_log(struct rev_info *opt, const char *sep)
{
- static char this_header[16384];
+ char *msgbuf = NULL;
+ unsigned long msgbuf_len = 0;
struct log_info *log = opt->loginfo;
struct commit *commit = log->commit, *parent = log->parent;
int abbrev = opt->diffopt.abbrev;
/*
* And then the pretty-printed message itself
*/
- len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
- sizeof(this_header), abbrev, subject,
+ len = pretty_print_commit(opt->commit_format, commit, ~0u,
+ &msgbuf, &msgbuf_len, abbrev, subject,
extra_headers, opt->date_mode);
if (opt->add_signoff)
- len = append_signoff(this_header, sizeof(this_header), len,
+ len = append_signoff(&msgbuf, &msgbuf_len, len,
opt->add_signoff);
- printf("%s%s%s", this_header, extra, sep);
+ printf("%s%s%s", msgbuf, extra, sep);
+ free(msgbuf);
}
int log_tree_diff_flush(struct rev_info *opt)
'validate the output.' \
'compare_diff_patch current expected'
+test_expect_success 'favour same basenames over different ones' '
+ cp path1 another-path &&
+ git add another-path &&
+ git commit -m 1 &&
+ git rm path1 &&
+ mkdir subdir &&
+ git mv another-path subdir/path1 &&
+ git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+
+test_expect_success 'favour same basenames even with minor differences' '
+ git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
+ git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+
test_done
--- /dev/null
+#!/bin/sh
+
+test_description='git-filter-branch'
+. ./test-lib.sh
+
+make_commit () {
+ lower=$(echo $1 | tr A-Z a-z)
+ echo $lower > $lower
+ git add $lower
+ test_tick
+ git commit -m $1
+ git tag $1
+}
+
+test_expect_success 'setup' '
+ make_commit A
+ make_commit B
+ git checkout -b branch B
+ make_commit D
+ make_commit E
+ git checkout master
+ make_commit C
+ git checkout branch
+ git merge C
+ git tag F
+ make_commit G
+ make_commit H
+'
+
+H=$(git-rev-parse H)
+
+test_expect_success 'rewrite identically' '
+ git-filter-branch H2
+'
+
+test_expect_success 'result is really identical' '
+ test $H = $(git-rev-parse H2)
+'
+
+test_expect_success 'rewrite, renaming a specific file' '
+ git-filter-branch --tree-filter "mv d doh || :" H3
+'
+
+test_expect_success 'test that the file was renamed' '
+ test d = $(git show H3:doh)
+'
+
+git tag oldD H3~4
+test_expect_success 'rewrite one branch, keeping a side branch' '
+ git-filter-branch --tree-filter "mv b boh || :" modD D..oldD
+'
+
+test_expect_success 'common ancestor is still common (unchanged)' '
+ test "$(git-merge-base modD D)" = "$(git-rev-parse B)"
+'
+
+test_expect_success 'filter subdirectory only' '
+ mkdir subdir &&
+ touch subdir/new &&
+ git add subdir/new &&
+ test_tick &&
+ git commit -m "subdir" &&
+ echo H > a &&
+ test_tick &&
+ git commit -m "not subdir" a &&
+ echo A > subdir/new &&
+ test_tick &&
+ git commit -m "again subdir" subdir/new &&
+ git rm a &&
+ test_tick &&
+ git commit -m "again not subdir" &&
+ git-filter-branch --subdirectory-filter subdir sub
+'
+
+test_expect_success 'subdirectory filter result looks okay' '
+ test 2 = $(git-rev-list sub | wc -l) &&
+ git show sub:new &&
+ ! git show sub:subdir
+'
+
+test_expect_success 'setup and filter history that requires --full-history' '
+ git checkout master &&
+ mkdir subdir &&
+ echo A > subdir/new &&
+ git add subdir/new &&
+ test_tick &&
+ git commit -m "subdir on master" subdir/new &&
+ git rm a &&
+ test_tick &&
+ git commit -m "again subdir on master" &&
+ git merge branch &&
+ git-filter-branch --subdirectory-filter subdir sub-master
+'
+
+test_expect_success 'subdirectory filter result looks okay' '
+ test 3 = $(git-rev-list -1 --parents sub-master | wc -w) &&
+ git show sub-master^:new &&
+ git show sub-master^2:new &&
+ ! git show sub:subdir
+'
+
+test_expect_success 'use index-filter to move into a subdirectory' '
+ git-filter-branch --index-filter \
+ "git-ls-files -s | sed \"s-\\t-&newsubdir/-\" |
+ GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
+ git-update-index --index-info &&
+ mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" directorymoved &&
+ test -z "$(git diff HEAD directorymoved:newsubdir)"'
+
+test_done
static inline int tree_entry_len(const char *name, const unsigned char *sha1)
{
- return (char *)sha1 - (char *)name - 1;
+ return (const char *)sha1 - name - 1;
}
void update_tree_entry(struct tree_desc *);