From d67778eccdf778f0fc252aaade8a47c23bfebbd6 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Fri, 26 Jan 2007 14:13:46 +0000 Subject: [PATCH 01/17] Allow the tag signing key to be specified in the config file I did this: $ git tag -s test-sign gpg: skipped "Andy Parkins ": secret key not available gpg: signing failed: secret key not available failed to sign the tag with GPG. The problem is that I have used the comment field in my key's UID definition. $ gpg --list-keys andy pub 1024D/4F712F6D 2003-08-14 uid Andy Parkins (Google) So when git-tag looks for "Andy Parkins "; obviously it's not going to be found. There shouldn't be a requirement that I use the same form of my name in my git repository and my gpg key - I might want to be formal (Andrew) in my gpg key and informal (Andy) in the repository. Further I might have multiple keys in my keyring, and might want to use one that doesn't match up with the address I use in commit messages. This patch adds a configuration entry "user.signingkey" which, if present, will be passed to the "-u" switch for gpg, allowing the tag signing key to be overridden. If the entry is not present, the fallback is the original method, which means existing behaviour will continue untouched. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- Documentation/config.txt | 7 +++++++ Documentation/git-tag.txt | 10 ++++++++++ git-tag.sh | 6 ++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 3f2fa09a87..6ea7c76a6a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -469,6 +469,13 @@ user.name:: Can be overridden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME' environment variables. See gitlink:git-commit-tree[1]. +user.signingkey:: + If gitlink:git-tag[1] is not selecting the key you want it to + automatically when creating a signed tag, you can override the + default selection with this variable. This option is passed + unchanged to gpg's --local-user parameter, so you may specify a key + using any method that gpg supports. + whatchanged.difftree:: The default gitlink:git-diff-tree[1] arguments to be used for gitlink:git-whatchanged[1]. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 13c7aefbf3..3f01e0bfc5 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -70,6 +70,16 @@ OPTIONS Take the tag message from the given file. Use '-' to read the message from the standard input. +CONFIGURATION +------------- +By default, git-tag in sign-with-default mode (-s) will use your +committer identity (of the form "Your Name ") to +find a key. If you want to use a different default key, you can specify +it in the repository configuration as follows: + +[user] + signingkey = + Author ------ Written by Linus Torvalds , diff --git a/git-tag.sh b/git-tag.sh index 94499c9b36..988bf4c6a6 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -112,7 +112,9 @@ git-check-ref-format "tags/$name" || object=$(git-rev-parse --verify --default HEAD "$@") || exit 1 type=$(git-cat-file -t $object) || exit 1 tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1 -: ${username:=$(expr "z$tagger" : 'z\(.*>\)')} + +keyid=$(git-repo-config user.signingkey) || + keyid=$(expr "z$tagger" : 'z\(.*>\)') trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0 @@ -139,7 +141,7 @@ if [ "$annotate" ]; then cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG if [ "$signed" ]; then - gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP && + gpg -bsa -u "$keyid" "$GIT_DIR"/TAG_TMP && cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP || die "failed to sign the tag with GPG." fi From 16507fcf0a9991976815972faa253d77804d6f35 Mon Sep 17 00:00:00 2001 From: Bill Lear Date: Sat, 27 Jan 2007 07:21:53 -0600 Subject: [PATCH 02/17] Document --check option to git diff. Signed-off-by: Bill Lear Signed-off-by: Junio C Hamano --- Documentation/SubmittingPatches | 3 ++- Documentation/diff-options.txt | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 41b76d8a4a..ce85d06c62 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -23,7 +23,8 @@ probably need to split up your commit to finer grained pieces. Oh, another thing. I am picky about whitespaces. Make sure your changes do not trigger errors with the sample pre-commit hook shipped -in templates/hooks--pre-commit. +in templates/hooks--pre-commit. To help ensure this does not happen, +run git diff --check on your changes before you commit. (2) Generate your patch using git tools out of your commits. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index da1cc60e97..019a39f2bf 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -58,6 +58,10 @@ Turn off rename detection, even when the configuration file gives the default to do so. +--check:: + Warn if changes introduce trailing whitespace + or an indent that uses a space before a tab. + --full-index:: Instead of the first handful characters, show full object name of pre- and post-image blob on the "index" From 903b45fe1808f72e712a118bf115b9dc07d02420 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 27 Jan 2007 22:40:36 -0500 Subject: [PATCH 03/17] git-log -g --pretty=oneline should display the reflog message In the context of reflog output the reflog message is more useful than the commit message's first line. When relevant the reflog message will contain that line anyway. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- log-tree.c | 7 ++++++- reflog-walk.c | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/log-tree.c b/log-tree.c index c0fa096327..d8ca36bbdd 100644 --- a/log-tree.c +++ b/log-tree.c @@ -224,9 +224,14 @@ void show_log(struct rev_info *opt, const char *sep) printf("%s", diff_get_color(opt->diffopt.color_diff, DIFF_RESET)); putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); - if (opt->reflog_info) + if (opt->reflog_info) { show_reflog_message(opt->reflog_info, opt->commit_format == CMIT_FMT_ONELINE);; + if (opt->commit_format == CMIT_FMT_ONELINE) { + printf("%s", sep); + return; + } + } } /* diff --git a/reflog-walk.c b/reflog-walk.c index 8e2cd2fcf6..82621601d6 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -233,7 +233,7 @@ void show_reflog_message(struct reflog_walk_info* info, int oneline) else printf("%d", commit_reflog->reflogs->nr - 2 - commit_reflog->recno); - printf("}: "); + printf("}: %s", info->message); } else { printf("Reflog: %s@{", commit_reflog->reflogs->ref); From 01754769ab9bf9ff2fe2b12590e7ee2c7c3448ca Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Jan 2007 00:50:53 -0800 Subject: [PATCH 04/17] Don't force everybody to call setup_ident(). Back when only handful commands that created commit and tag were the only users of committer identity information, it made sense to explicitly call setup_ident() to pre-fill the default value from the gecos information. But it is much simpler for programs to make the call automatic when get_ident() is called these days, since many more programs want to use the information when updating the reflog. Signed-off-by: Junio C Hamano --- builtin-branch.c | 1 - builtin-commit-tree.c | 1 - builtin-log.c | 1 - builtin-update-ref.c | 1 - cache.h | 1 - fetch-pack.c | 1 - http-fetch.c | 1 - http-push.c | 1 - ident.c | 51 +++++++++++++++++++++++++++++-------------- local-fetch.c | 1 - receive-pack.c | 1 - ssh-fetch.c | 1 - var.c | 1 - 13 files changed, 35 insertions(+), 28 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index 25ffa5491c..d60690bb08 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -394,7 +394,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix) int kinds = REF_LOCAL_BRANCH; int i; - setup_ident(); git_config(git_branch_config); for (i = 1; i < argc; i++) { diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 0651e5927e..2a818a0a2c 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -94,7 +94,6 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) unsigned int size; int encoding_is_utf8; - setup_ident(); git_config(git_default_config); if (argc < 2) diff --git a/builtin-log.c b/builtin-log.c index 56acc137f0..982d871887 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -380,7 +380,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char message_id[1024]; char ref_message_id[1024]; - setup_ident(); git_config(git_format_config); init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; diff --git a/builtin-update-ref.c b/builtin-update-ref.c index b34e5987dd..1461937cb9 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -13,7 +13,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) int i, delete; delete = 0; - setup_ident(); git_config(git_default_config); for (i = 1; i < argc; i++) { diff --git a/cache.h b/cache.h index 9486132ac5..9873ee97d9 100644 --- a/cache.h +++ b/cache.h @@ -319,7 +319,6 @@ int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); -extern int setup_ident(void); extern const char *git_author_info(int); extern const char *git_committer_info(int); diff --git a/fetch-pack.c b/fetch-pack.c index 83a1d7b319..c787106764 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -670,7 +670,6 @@ int main(int argc, char **argv) struct stat st; setup_git_directory(); - setup_ident(); git_config(fetch_pack_config); if (0 <= transfer_unpack_limit) diff --git a/http-fetch.c b/http-fetch.c index 67dfb0a033..efd494a47f 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -1003,7 +1003,6 @@ int main(int argc, const char **argv) int arg = 1; int rc = 0; - setup_ident(); setup_git_directory(); git_config(git_default_config); diff --git a/http-push.c b/http-push.c index 0a15f53782..b128c0146c 100644 --- a/http-push.c +++ b/http-push.c @@ -2299,7 +2299,6 @@ int main(int argc, char **argv) struct ref *ref; setup_git_directory(); - setup_ident(); remote = xcalloc(sizeof(*remote), 1); diff --git a/ident.c b/ident.c index f9677905e5..6de7eeae4d 100644 --- a/ident.c +++ b/ident.c @@ -43,19 +43,13 @@ static void copy_gecos(struct passwd *w, char *name, int sz) } -int setup_ident(void) +static void copy_email(struct passwd *pw) { - int len; - struct passwd *pw = getpwuid(getuid()); - - if (!pw) - die("You don't exist. Go away!"); - - /* Get the name ("gecos") */ - copy_gecos(pw, git_default_name, sizeof(git_default_name)); - - /* Make up a fake email address (name + '@' + hostname [+ '.' + domainname]) */ - len = strlen(pw->pw_name); + /* + * Make up a fake email address + * (name + '@' + hostname [+ '.' + domainname]) + */ + int len = strlen(pw->pw_name); if (len > sizeof(git_default_email)/2) die("Your sysadmin must hate you!"); memcpy(git_default_email, pw->pw_name, len); @@ -68,13 +62,37 @@ int setup_ident(void) len = strlen(git_default_email); git_default_email[len++] = '.'; if (he && (domainname = strchr(he->h_name, '.'))) - strlcpy(git_default_email + len, domainname + 1, sizeof(git_default_email) - len); + strlcpy(git_default_email + len, domainname + 1, + sizeof(git_default_email) - len); else - strlcpy(git_default_email + len, "(none)", sizeof(git_default_email) - len); + strlcpy(git_default_email + len, "(none)", + sizeof(git_default_email) - len); } +} + +static void setup_ident(void) +{ + struct passwd *pw = NULL; + + /* Get the name ("gecos") */ + if (!git_default_name[0]) { + pw = getpwuid(getuid()); + if (!pw) + die("You don't exist. Go away!"); + copy_gecos(pw, git_default_name, sizeof(git_default_name)); + } + + if (!git_default_email[0]) { + if (!pw) + pw = getpwuid(getuid()); + if (!pw) + die("You don't exist. Go away!"); + copy_email(pw); + } + /* And set the default date */ - datestamp(git_default_date, sizeof(git_default_date)); - return 0; + if (!git_default_date[0]) + datestamp(git_default_date, sizeof(git_default_date)); } static int add_raw(char *buf, int size, int offset, const char *str) @@ -174,6 +192,7 @@ static const char *get_ident(const char *name, const char *email, char date[50]; int i; + setup_ident(); if (!name) name = git_default_name; if (!email) diff --git a/local-fetch.c b/local-fetch.c index cf99cb72dd..7cfe8b3587 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -210,7 +210,6 @@ int main(int argc, const char **argv) char **commit_id; int arg = 1; - setup_ident(); setup_git_directory(); git_config(git_default_config); diff --git a/receive-pack.c b/receive-pack.c index 7d263262d3..7311c822dd 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -429,7 +429,6 @@ int main(int argc, char **argv) if (is_repository_shallow()) die("attempt to push into a shallow repository"); - setup_ident(); git_config(receive_pack_config); if (0 <= transfer_unpack_limit) diff --git a/ssh-fetch.c b/ssh-fetch.c index 4c172b6824..bdf51a7a14 100644 --- a/ssh-fetch.c +++ b/ssh-fetch.c @@ -124,7 +124,6 @@ int main(int argc, char **argv) prog = getenv("GIT_SSH_PUSH"); if (!prog) prog = "git-ssh-upload"; - setup_ident(); setup_git_directory(); git_config(git_default_config); diff --git a/var.c b/var.c index 39977b949a..e585e59d31 100644 --- a/var.c +++ b/var.c @@ -56,7 +56,6 @@ int main(int argc, char **argv) } setup_git_directory(); - setup_ident(); val = NULL; if (strcmp(argv[1], "-l") == 0) { From 717d1462ba0ebc430775eccaae65eb9b8234eb32 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 28 Jan 2007 01:34:06 -0800 Subject: [PATCH 05/17] git-blame --incremental This adds --incremental option to help GUI porcelains to show the result from git-blame incrementally. The output gives the origin information in the same format as the porcelain format. The first line has commit object name, the line number of the first line in the group in the original file, the line number of that file in the final image, and number of lines in the group. Then subsequent lines show the metainformation for the commit when the commit is shown for the first time, except the filename information is always shown (we cannot even make it conditional to -C option as blame always follows the renaming of the file wholesale). Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-blame.c | 173 ++++++++++++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 66 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 4a1accf13c..54ab675624 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -13,6 +13,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "quote.h" #include "xdiff-interface.h" static char blame_usage[] = @@ -27,6 +28,7 @@ static char blame_usage[] = " -p, --porcelain Show in a format designed for machine consumption\n" " -L n,m Process only line range n,m, counting from 1\n" " -M, -C Find line movements within and across files\n" +" --incremental Show blame entries as we find them, incrementally\n" " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; static int longest_file; @@ -36,6 +38,7 @@ static int max_digits; static int max_score_digits; static int show_root; static int blank_boundary; +static int incremental; #ifndef DEBUG #define DEBUG 0 @@ -1069,72 +1072,6 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) origin_decref(parent_origin[i]); } -static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) -{ - while (1) { - struct blame_entry *ent; - struct commit *commit; - struct origin *suspect = NULL; - - /* find one suspect to break down */ - for (ent = sb->ent; !suspect && ent; ent = ent->next) - if (!ent->guilty) - suspect = ent->suspect; - if (!suspect) - return; /* all done */ - - origin_incref(suspect); - commit = suspect->commit; - if (!commit->object.parsed) - parse_commit(commit); - if (!(commit->object.flags & UNINTERESTING) && - !(revs->max_age != -1 && commit->date < revs->max_age)) - pass_blame(sb, suspect, opt); - else { - commit->object.flags |= UNINTERESTING; - if (commit->object.parsed) - mark_parents_uninteresting(commit); - } - /* treat root commit as boundary */ - if (!commit->parents && !show_root) - commit->object.flags |= UNINTERESTING; - - /* Take responsibility for the remaining entries */ - for (ent = sb->ent; ent; ent = ent->next) - if (!cmp_suspect(ent->suspect, suspect)) - ent->guilty = 1; - origin_decref(suspect); - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); - } -} - -static const char *format_time(unsigned long time, const char *tz_str, - int show_raw_time) -{ - static char time_buf[128]; - time_t t = time; - int minutes, tz; - struct tm *tm; - - if (show_raw_time) { - sprintf(time_buf, "%lu %s", time, tz_str); - return time_buf; - } - - tz = atoi(tz_str); - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; - tm = gmtime(&t); - - strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); - strcat(time_buf, tz_str); - return time_buf; -} - struct commit_info { char *author; @@ -1245,6 +1182,105 @@ static void get_commit_info(struct commit *commit, summary_buf[len] = 0; } +static void found_guilty_entry(struct blame_entry *ent) +{ + if (ent->guilty) + return; + ent->guilty = 1; + if (incremental) { + struct origin *suspect = ent->suspect; + + printf("%s %d %d %d\n", + sha1_to_hex(suspect->commit->object.sha1), + ent->s_lno + 1, ent->lno + 1, ent->num_lines); + if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { + struct commit_info ci; + suspect->commit->object.flags |= METAINFO_SHOWN; + get_commit_info(suspect->commit, &ci, 1); + printf("author %s\n", ci.author); + printf("author-mail %s\n", ci.author_mail); + printf("author-time %lu\n", ci.author_time); + printf("author-tz %s\n", ci.author_tz); + printf("committer %s\n", ci.committer); + printf("committer-mail %s\n", ci.committer_mail); + printf("committer-time %lu\n", ci.committer_time); + printf("committer-tz %s\n", ci.committer_tz); + printf("summary %s\n", ci.summary); + if (suspect->commit->object.flags & UNINTERESTING) + printf("boundary\n"); + } + printf("filename "); + write_name_quoted(NULL, 0, suspect->path, 1, stdout); + putchar('\n'); + } +} + +static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) +{ + while (1) { + struct blame_entry *ent; + struct commit *commit; + struct origin *suspect = NULL; + + /* find one suspect to break down */ + for (ent = sb->ent; !suspect && ent; ent = ent->next) + if (!ent->guilty) + suspect = ent->suspect; + if (!suspect) + return; /* all done */ + + origin_incref(suspect); + commit = suspect->commit; + if (!commit->object.parsed) + parse_commit(commit); + if (!(commit->object.flags & UNINTERESTING) && + !(revs->max_age != -1 && commit->date < revs->max_age)) + pass_blame(sb, suspect, opt); + else { + commit->object.flags |= UNINTERESTING; + if (commit->object.parsed) + mark_parents_uninteresting(commit); + } + /* treat root commit as boundary */ + if (!commit->parents && !show_root) + commit->object.flags |= UNINTERESTING; + + /* Take responsibility for the remaining entries */ + for (ent = sb->ent; ent; ent = ent->next) + if (!cmp_suspect(ent->suspect, suspect)) + found_guilty_entry(ent); + origin_decref(suspect); + + if (DEBUG) /* sanity */ + sanity_check_refcnt(sb); + } +} + +static const char *format_time(unsigned long time, const char *tz_str, + int show_raw_time) +{ + static char time_buf[128]; + time_t t = time; + int minutes, tz; + struct tm *tm; + + if (show_raw_time) { + sprintf(time_buf, "%lu %s", time, tz_str); + return time_buf; + } + + tz = atoi(tz_str); + minutes = tz < 0 ? -tz : tz; + minutes = (minutes / 100)*60 + (minutes % 100); + minutes = tz < 0 ? -minutes : minutes; + t = time + minutes * 60; + tm = gmtime(&t); + + strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); + strcat(time_buf, tz_str); + return time_buf; +} + #define OUTPUT_ANNOTATE_COMPAT 001 #define OUTPUT_LONG_OBJECT_NAME 002 #define OUTPUT_RAW_TIMESTAMP 004 @@ -1717,6 +1753,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) die("More than one '-L n,m' option given"); bottomtop = arg; } + else if (!strcmp("--incremental", arg)) + incremental = 1; else if (!strcmp("--score-debug", arg)) output_option |= OUTPUT_SHOW_SCORE; else if (!strcmp("-f", arg) || @@ -1907,6 +1945,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix) assign_blame(&sb, &revs, opt); + if (incremental) + return 0; + coalesce(&sb); if (!(output_option & OUTPUT_PORCELAIN)) From 46e5e69d5f2e8ae87a29953531a3d1bd7cb0292f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Jan 2007 01:42:31 -0800 Subject: [PATCH 06/17] git-blame --porcelain: quote filename in c-style when needed. Otherwise a pathname that has funny characters such as LF would screw up the parsing programs of the output. Strictly speaking, this is not backward compatible, but the current output for pathnames that have embedded LF and such cannot be sanely parsed anyway, and pathnames that only use characters from the portable pathname character set won't be affected. Signed-off-by: Junio C Hamano --- builtin-blame.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 54ab675624..7a58ee303f 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1182,6 +1182,13 @@ static void get_commit_info(struct commit *commit, summary_buf[len] = 0; } +static void write_filename_info(const char *path) +{ + printf("filename "); + write_name_quoted(NULL, 0, path, 1, stdout); + putchar('\n'); +} + static void found_guilty_entry(struct blame_entry *ent) { if (ent->guilty) @@ -1209,9 +1216,7 @@ static void found_guilty_entry(struct blame_entry *ent) if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); } - printf("filename "); - write_name_quoted(NULL, 0, suspect->path, 1, stdout); - putchar('\n'); + write_filename_info(suspect->path); } } @@ -1315,13 +1320,13 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) printf("committer-mail %s\n", ci.committer_mail); printf("committer-time %lu\n", ci.committer_time); printf("committer-tz %s\n", ci.committer_tz); - printf("filename %s\n", suspect->path); + write_filename_info(suspect->path); printf("summary %s\n", ci.summary); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); } else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH) - printf("filename %s\n", suspect->path); + write_filename_info(suspect->path); cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { From 237fb6ca7cbe45f88523ad367d0d703a039dff2d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 25 Jan 2007 12:39:54 -0500 Subject: [PATCH 07/17] Teach git-describe to display distances from tags. If you get two different describes at different times from a non-rewinding branch and they both come up with the same tag name, you can tell which is the 'newer' one by distance. This is rather common in practice, so its incredibly useful. [jc: still needs documentation and fixups when traversal gives up early.] Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-describe.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin-describe.c b/builtin-describe.c index f3ac2d5f89..aedbc78dac 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -191,8 +191,9 @@ static void describe(const char *arg, int last_one) if (abbrev == 0) printf("%s\n", all_matches[0].name->path ); else - printf("%s-g%s\n", all_matches[0].name->path, - find_unique_abbrev(cmit->object.sha1, abbrev)); + printf("%s-%d-g%s\n", all_matches[0].name->path, + all_matches[0].depth, + find_unique_abbrev(cmit->object.sha1, abbrev)); if (!last_one) clear_commit_marks(cmit, -1); From 1891261ed3ed52022ae5322b4db338f9d5112909 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 26 Jan 2007 23:24:07 -0800 Subject: [PATCH 08/17] Update describe documentation. Signed-off-by: Junio C Hamano --- Documentation/git-describe.txt | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index b87783cf09..47a583d3a6 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -14,8 +14,8 @@ DESCRIPTION ----------- The command finds the most recent tag that is reachable from a commit, and if the commit itself is pointed at by the tag, shows -the tag. Otherwise, it suffixes the tag name with abbreviated -object name of the commit. +the tag. Otherwise, it suffixes the tag name with the number of +additional commits and the abbreviated object name of the commit. OPTIONS @@ -52,12 +52,18 @@ EXAMPLES With something like git.git current tree, I get: [torvalds@g5 git]$ git-describe parent - v1.0.4-g2414721b + v1.0.4-14-g2414721 i.e. the current head of my "parent" branch is based on v1.0.4, -but since it has a few commits on top of that, it has added the -git hash of the thing to the end: "-g" + 8-char shorthand for -the commit `2414721b194453f058079d897d13c4e377f92dc6`. +but since it has a handful commits on top of that, +describe has added the number of additional commits ("14") and +an abbreviated object name for the commit itself ("2414721") +at the end. + +The number of additional commits is the number +of commits which would be displayed by "git log v1.0.4..parent". +The hash suffix is "-g" + 7-char abbreviation for the tip commit +of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`). Doing a "git-describe" on a tag-name will just show the tag name: @@ -68,10 +74,16 @@ With --all, the command can use branch heads as references, so the output shows the reference path as well: [torvalds@g5 git]$ git describe --all --abbrev=4 v1.0.5^2 - tags/v1.0.0-g975b + tags/v1.0.0-21-g975b [torvalds@g5 git]$ git describe --all HEAD^ - heads/lt/describe-g975b + heads/lt/describe-7-g975b + +With --abbrev set to 0, the command can be used to find the +closest tagname without any suffix: + + [torvalds@g5 git]$ git describe --abbrev=0 v1.0.5^2 + tags/v1.0.0 SEARCH STRATEGY --------------- @@ -97,7 +109,8 @@ will be the smallest number of commits possible. Author ------ Written by Linus Torvalds , but somewhat -butchered by Junio C Hamano +butchered by Junio C Hamano . Later significantly +updated by Shawn Pearce . Documentation -------------- From 1b600e659abc7e409c9d830e332d3cef01062c1c Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 27 Jan 2007 01:54:21 -0500 Subject: [PATCH 09/17] Compute accurate distances in git-describe before output. My prior change to git-describe attempts to print the distance between the input commit and the best matching tag, but this distance was usually only an estimate as we always aborted revision walking as soon as we overflowed the configured limit on the number of possible tags (as set by --candidates). Displaying an estimated distance is not very useful and can just be downright confusing. Most users (heck, most Git developers) don't immediately understand why this distance differs from the output of common tools such as `git rev-list | wc -l`. Even worse, the estimated distance could change in the future (including decreasing despite no rebase occuring) if we find more possible tags earlier on during traversal. (This could happen if more tags are merged into the branch between queries.) These factors basically make an estimated distance useless. Fortunately we are usually most of the way through an accurate distance computation by the time we abort (due to reaching the current --candidates limit). This means we can simply finish counting out the revisions still in our commit queue to present the accurate distance at the end. The number of commits remaining in the commit queue is probably less than the number of commits already traversed, so finishing out the count is not likely to take very long. This final distance will then always match the output of `git rev-list | wc -l`. We can easily reduce the total number of commits that need to be walked at the end by stopping as soon as all of the commits in the commit queue are flagged as being merged into the already selected best possible tag. If that's true then there are no remaining unseen commits which can contribute to our best possible tag's depth counter, so further traversal is useless. Basic testing on my Mac OS X system shows there is no noticable performance difference between this accurate distance counting version of git-describe and the prior version of git-describe, at least when run on git.git. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-describe.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/builtin-describe.c b/builtin-describe.c index aedbc78dac..bcc645622a 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -91,6 +91,39 @@ static int compare_pt(const void *a_, const void *b_) return 0; } +static unsigned long finish_depth_computation( + struct commit_list **list, + struct possible_tag *best) +{ + unsigned long seen_commits = 0; + while (*list) { + struct commit *c = pop_commit(list); + struct commit_list *parents = c->parents; + seen_commits++; + if (c->object.flags & best->flag_within) { + struct commit_list *a = *list; + while (a) { + struct commit *i = a->item; + if (!(i->object.flags & best->flag_within)) + break; + a = a->next; + } + if (!a) + break; + } else + best->depth++; + while (parents) { + struct commit *p = parents->item; + parse_commit(p); + if (!(p->object.flags & SEEN)) + insert_by_date(p, list); + p->object.flags |= c->object.flags; + parents = parents->next; + } + } + return seen_commits; +} + static void describe(const char *arg, int last_one) { unsigned char sha1[20]; @@ -166,12 +199,19 @@ static void describe(const char *arg, int last_one) parents = parents->next; } } - free_commit_list(list); if (!match_cnt) die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1)); qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt); + + if (gave_up_on) { + insert_by_date(gave_up_on, &list); + seen_commits--; + } + seen_commits += finish_depth_computation(&list, &all_matches[0]); + free_commit_list(list); + if (debug) { for (cur_match = 0; cur_match < match_cnt; cur_match++) { struct possible_tag *t = &all_matches[cur_match]; From 9a13f0b71b887af42c3be854344f185c6dfa1d0d Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:05 -0500 Subject: [PATCH 10/17] make reflog filename independent from struct ref_lock This allows for ref_log_write() to be used in a more flexible way, and is needed for future changes. This is only code reorg with no behavior change. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-reflog.c | 10 ++++++---- refs.c | 40 ++++++++++++++++++++-------------------- refs.h | 1 - 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/builtin-reflog.c b/builtin-reflog.c index b443ed9ef6..b6612a90ed 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -242,7 +242,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, struct cmd_reflog_expire_cb *cmd = cb_data; struct expire_reflog_cb cb; struct ref_lock *lock; - char *newlog_path = NULL; + char *log_file, *newlog_path = NULL; int status = 0; if (strncmp(ref, "refs/", 5)) @@ -255,7 +255,8 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, lock = lock_ref_sha1(ref + 5, sha1); if (!lock) return error("cannot lock ref '%s'", ref); - if (!file_exists(lock->log_file)) + log_file = xstrdup(git_path("logs/%s", ref)); + if (!file_exists(log_file)) goto finish; if (!cmd->dry_run) { newlog_path = xstrdup(git_path("logs/%s.lock", ref)); @@ -271,13 +272,14 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, if (fclose(cb.newlog)) status |= error("%s: %s", strerror(errno), newlog_path); - if (rename(newlog_path, lock->log_file)) { + if (rename(newlog_path, log_file)) { status |= error("cannot rename %s to %s", - newlog_path, lock->log_file); + newlog_path, log_file); unlink(newlog_path); } } free(newlog_path); + free(log_file); unlock_ref(lock); return status; } diff --git a/refs.c b/refs.c index 12e46b8bbe..3b295f3806 100644 --- a/refs.c +++ b/refs.c @@ -680,7 +680,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock->lk = xcalloc(1, sizeof(struct lock_file)); lock->ref_name = xstrdup(ref); - lock->log_file = xstrdup(git_path("logs/%s", ref)); ref_file = git_path("%s", ref); lock->force_write = lstat(ref_file, &st) && errno == ENOENT; @@ -776,10 +775,10 @@ int delete_ref(const char *refname, unsigned char *sha1) */ ret |= repack_without_ref(refname); - err = unlink(lock->log_file); + err = unlink(git_path("logs/%s", lock->ref_name)); if (err && errno != ENOENT) fprintf(stderr, "warning: unlink(%s) failed: %s", - lock->log_file, strerror(errno)); + git_path("logs/%s", lock->ref_name), strerror(errno)); invalidate_cached_refs(); unlock_ref(lock); return ret; @@ -920,47 +919,48 @@ void unlock_ref(struct ref_lock *lock) rollback_lock_file(lock->lk); } free(lock->ref_name); - free(lock->log_file); free(lock); } -static int log_ref_write(struct ref_lock *lock, - const unsigned char *sha1, const char *msg) +static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg) { int logfd, written, oflags = O_APPEND | O_WRONLY; unsigned maxlen, len; int msglen; - char *logrec; + char *log_file, *logrec; const char *committer; if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); + log_file = git_path("logs/%s", ref_name); + if (log_all_ref_updates && - (!strncmp(lock->ref_name, "refs/heads/", 11) || - !strncmp(lock->ref_name, "refs/remotes/", 13))) { - if (safe_create_leading_directories(lock->log_file) < 0) + (!strncmp(ref_name, "refs/heads/", 11) || + !strncmp(ref_name, "refs/remotes/", 13))) { + if (safe_create_leading_directories(log_file) < 0) return error("unable to create directory for %s", - lock->log_file); + log_file); oflags |= O_CREAT; } - logfd = open(lock->log_file, oflags, 0666); + logfd = open(log_file, oflags, 0666); if (logfd < 0) { if (!(oflags & O_CREAT) && errno == ENOENT) return 0; if ((oflags & O_CREAT) && errno == EISDIR) { - if (remove_empty_directories(lock->log_file)) { + if (remove_empty_directories(log_file)) { return error("There are still logs under '%s'", - lock->log_file); + log_file); } - logfd = open(lock->log_file, oflags, 0666); + logfd = open(log_file, oflags, 0666); } if (logfd < 0) return error("Unable to append to %s: %s", - lock->log_file, strerror(errno)); + log_file, strerror(errno)); } msglen = 0; @@ -982,8 +982,8 @@ static int log_ref_write(struct ref_lock *lock, maxlen = strlen(committer) + msglen + 100; logrec = xmalloc(maxlen); len = sprintf(logrec, "%s %s %s\n", - sha1_to_hex(lock->old_sha1), - sha1_to_hex(sha1), + sha1_to_hex(old_sha1), + sha1_to_hex(new_sha1), committer); if (msglen) len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1; @@ -991,7 +991,7 @@ static int log_ref_write(struct ref_lock *lock, free(logrec); close(logfd); if (written != len) - return error("Unable to append to %s", lock->log_file); + return error("Unable to append to %s", log_file); return 0; } @@ -1014,7 +1014,7 @@ int write_ref_sha1(struct ref_lock *lock, return -1; } invalidate_cached_refs(); - if (log_ref_write(lock, sha1, logmsg) < 0) { + if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0) { unlock_ref(lock); return -1; } diff --git a/refs.h b/refs.h index 33450f13e7..2d2ba149ab 100644 --- a/refs.h +++ b/refs.h @@ -3,7 +3,6 @@ struct ref_lock { char *ref_name; - char *log_file; struct lock_file *lk; unsigned char old_sha1[20]; int lock_fd; From 1655707c9ec56847945854f8645ed64f74159e99 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:06 -0500 Subject: [PATCH 11/17] lock_ref_sha1_basic(): remember the original name of a ref when resolving it A ref might be pointing to another ref but only the name of the last ref is remembered. Let's remember about the first name as well. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- refs.c | 2 ++ refs.h | 1 + 2 files changed, 3 insertions(+) diff --git a/refs.c b/refs.c index 3b295f3806..bd76ea65a9 100644 --- a/refs.c +++ b/refs.c @@ -680,6 +680,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char lock->lk = xcalloc(1, sizeof(struct lock_file)); lock->ref_name = xstrdup(ref); + lock->orig_ref_name = xstrdup(orig_ref); ref_file = git_path("%s", ref); lock->force_write = lstat(ref_file, &st) && errno == ENOENT; @@ -919,6 +920,7 @@ void unlock_ref(struct ref_lock *lock) rollback_lock_file(lock->lk); } free(lock->ref_name); + free(lock->orig_ref_name); free(lock); } diff --git a/refs.h b/refs.h index 2d2ba149ab..94a58b41fb 100644 --- a/refs.h +++ b/refs.h @@ -3,6 +3,7 @@ struct ref_lock { char *ref_name; + char *orig_ref_name; struct lock_file *lk; unsigned char old_sha1[20]; int lock_fd; From bd104db164d7f2a714aa0f1cdf89fd89fee6c00a Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:07 -0500 Subject: [PATCH 12/17] enable separate reflog for HEAD If HEAD is tied to a branch then both logs/HEAD and logs/heads/ are updated. This is also true for any symbolic refs in general, but only HEAD will see its reflog created automatically. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- refs.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/refs.c b/refs.c index bd76ea65a9..5b2ca086aa 100644 --- a/refs.c +++ b/refs.c @@ -940,7 +940,8 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, if (log_all_ref_updates && (!strncmp(ref_name, "refs/heads/", 11) || - !strncmp(ref_name, "refs/remotes/", 13))) { + !strncmp(ref_name, "refs/remotes/", 13) || + !strcmp(ref_name, "HEAD"))) { if (safe_create_leading_directories(log_file) < 0) return error("unable to create directory for %s", log_file); @@ -1016,7 +1017,9 @@ int write_ref_sha1(struct ref_lock *lock, return -1; } invalidate_cached_refs(); - if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0) { + if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 || + (strcmp(lock->ref_name, lock->orig_ref_name) && + log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) { unlock_ref(lock); return -1; } From e1dde3d06c7caa242dd4b419aebb9a9b7fee2d48 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:08 -0500 Subject: [PATCH 13/17] add reflog entries for HEAD when detached Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- git-checkout.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-checkout.sh b/git-checkout.sh index 8500f51ea2..ac378cdb1e 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -259,8 +259,9 @@ if [ "$?" -eq 0 ]; then # git update-ref --detach HEAD $new # or something like that... # - echo "$detached" >"$GIT_DIR/HEAD.new" && - mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" || + git-rev-parse HEAD >"$GIT_DIR/HEAD.new" && + mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" && + git-update-ref -m "checkout: moving to $arg" HEAD "$detached" || die "Cannot detach HEAD" if test -n "$detach_warn" then From 41b625b047ad5c537ca312d2f07c86bdd783a7b0 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:09 -0500 Subject: [PATCH 14/17] move create_symref() past log_ref_write() This doesn't change the code at all. It is done to make the next patch more obvious. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- refs.c | 94 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/refs.c b/refs.c index 5b2ca086aa..ec6fe29ebb 100644 --- a/refs.c +++ b/refs.c @@ -309,53 +309,6 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * return ref; } -int create_symref(const char *ref_target, const char *refs_heads_master) -{ - const char *lockpath; - char ref[1000]; - int fd, len, written; - const char *git_HEAD = git_path("%s", ref_target); - -#ifndef NO_SYMLINK_HEAD - if (prefer_symlink_refs) { - unlink(git_HEAD); - if (!symlink(refs_heads_master, git_HEAD)) - return 0; - fprintf(stderr, "no symlink - falling back to symbolic ref\n"); - } -#endif - - len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); - if (sizeof(ref) <= len) { - error("refname too long: %s", refs_heads_master); - return -1; - } - lockpath = mkpath("%s.lock", git_HEAD); - fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd < 0) { - error("Unable to open %s for writing", lockpath); - return -5; - } - written = write_in_full(fd, ref, len); - close(fd); - if (written != len) { - unlink(lockpath); - error("Unable to write to %s", lockpath); - return -2; - } - if (rename(lockpath, git_HEAD) < 0) { - unlink(lockpath); - error("Unable to create %s", git_HEAD); - return -3; - } - if (adjust_shared_perm(git_HEAD)) { - unlink(lockpath); - error("Unable to fix permissions on %s", lockpath); - return -4; - } - return 0; -} - int read_ref(const char *ref, unsigned char *sha1) { if (resolve_ref(ref, sha1, 1, NULL)) @@ -1033,6 +986,53 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } +int create_symref(const char *ref_target, const char *refs_heads_master) +{ + const char *lockpath; + char ref[1000]; + int fd, len, written; + const char *git_HEAD = git_path("%s", ref_target); + +#ifndef NO_SYMLINK_HEAD + if (prefer_symlink_refs) { + unlink(git_HEAD); + if (!symlink(refs_heads_master, git_HEAD)) + return 0; + fprintf(stderr, "no symlink - falling back to symbolic ref\n"); + } +#endif + + len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); + if (sizeof(ref) <= len) { + error("refname too long: %s", refs_heads_master); + return -1; + } + lockpath = mkpath("%s.lock", git_HEAD); + fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd < 0) { + error("Unable to open %s for writing", lockpath); + return -5; + } + written = write_in_full(fd, ref, len); + close(fd); + if (written != len) { + unlink(lockpath); + error("Unable to write to %s", lockpath); + return -2; + } + if (rename(lockpath, git_HEAD) < 0) { + unlink(lockpath); + error("Unable to create %s", git_HEAD); + return -3; + } + if (adjust_shared_perm(git_HEAD)) { + unlink(lockpath); + error("Unable to fix permissions on %s", lockpath); + return -4; + } + return 0; +} + static char *ref_msg(const char *line, const char *endp) { const char *ep; From 8b5157e40718a2ee1d645c342d93df4e66335479 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:10 -0500 Subject: [PATCH 15/17] add logref support to git-symbolic-ref Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- Documentation/git-symbolic-ref.txt | 6 +++++- builtin-branch.c | 3 ++- builtin-init-db.c | 2 +- builtin-symbolic-ref.c | 16 ++++++++++++++-- cache.h | 2 +- refs.c | 14 ++++++++++++-- 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index f93b79a85e..a88f722860 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -7,7 +7,7 @@ git-symbolic-ref - Read and modify symbolic refs SYNOPSIS -------- -'git-symbolic-ref' [-q] [] +'git-symbolic-ref' [-q] [-m ] [] DESCRIPTION ----------- @@ -31,6 +31,10 @@ OPTIONS symbolic ref but a detached HEAD; instead exit with non-zero status silently. +-m:: + Update the reflog for with . This is valid only + when creating or updating a symbolic ref. + NOTES ----- In the past, `.git/HEAD` was a symbolic link pointing at diff --git a/builtin-branch.c b/builtin-branch.c index d60690bb08..76f174fd89 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -381,7 +381,8 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (rename_ref(oldref, newref, logmsg)) die("Branch rename failed"); - if (!strcmp(oldname, head) && create_symref("HEAD", newref)) + /* no need to pass logmsg here as HEAD didn't really move */ + if (!strcmp(oldname, head) && create_symref("HEAD", newref, NULL)) die("Branch renamed to %s, but HEAD is not updated!", newname); } diff --git a/builtin-init-db.c b/builtin-init-db.c index 1865489381..12e43d0db4 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -231,7 +231,7 @@ static int create_default_files(const char *git_dir, const char *template_path) strcpy(path + len, "HEAD"); reinit = !read_ref("HEAD", sha1); if (!reinit) { - if (create_symref("HEAD", "refs/heads/master") < 0) + if (create_symref("HEAD", "refs/heads/master", NULL) < 0) exit(1); } diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 227c9d4a62..d41b40640b 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -3,7 +3,7 @@ #include "refs.h" static const char git_symbolic_ref_usage[] = -"git-symbolic-ref [-q] name [ref]"; +"git-symbolic-ref [-q] [-m ] name [ref]"; static void check_symref(const char *HEAD, int quiet) { @@ -25,6 +25,7 @@ static void check_symref(const char *HEAD, int quiet) int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { int quiet = 0; + const char *msg = NULL; git_config(git_default_config); @@ -34,6 +35,17 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) break; else if (!strcmp("-q", arg)) quiet = 1; + else if (!strcmp("-m", arg)) { + argc--; + argv++; + if (argc <= 1) + break; + msg = argv[1]; + if (!*msg) + die("Refusing to perform update with empty message"); + if (strchr(msg, '\n')) + die("Refusing to perform update with \\n in message"); + } else if (!strcmp("--", arg)) { argc--; argv++; @@ -50,7 +62,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[1], quiet); break; case 3: - create_symref(argv[1], argv[2]); + create_symref(argv[1], argv[2], msg); break; default: usage(git_symbolic_ref_usage); diff --git a/cache.h b/cache.h index 9873ee97d9..201704bacf 100644 --- a/cache.h +++ b/cache.h @@ -302,7 +302,7 @@ extern int read_ref(const char *filename, unsigned char *sha1); extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); -extern int create_symref(const char *ref, const char *refs_heads_master); +extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg); extern int validate_headref(const char *ref); extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); diff --git a/refs.c b/refs.c index ec6fe29ebb..591318501f 100644 --- a/refs.c +++ b/refs.c @@ -986,18 +986,23 @@ int write_ref_sha1(struct ref_lock *lock, return 0; } -int create_symref(const char *ref_target, const char *refs_heads_master) +int create_symref(const char *ref_target, const char *refs_heads_master, + const char *logmsg) { const char *lockpath; char ref[1000]; int fd, len, written; const char *git_HEAD = git_path("%s", ref_target); + unsigned char old_sha1[20], new_sha1[20]; + + if (logmsg && read_ref(ref_target, old_sha1)) + hashclr(old_sha1); #ifndef NO_SYMLINK_HEAD if (prefer_symlink_refs) { unlink(git_HEAD); if (!symlink(refs_heads_master, git_HEAD)) - return 0; + goto done; fprintf(stderr, "no symlink - falling back to symbolic ref\n"); } #endif @@ -1030,6 +1035,11 @@ int create_symref(const char *ref_target, const char *refs_heads_master) error("Unable to fix permissions on %s", lockpath); return -4; } + + done: + if (logmsg && !read_ref(refs_heads_master, new_sha1)) + log_ref_write(ref_target, old_sha1, new_sha1, logmsg); + return 0; } From 47fc52e2876ed3a54cd4b77e0b0c4875fa3317b8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 26 Jan 2007 17:49:00 -0800 Subject: [PATCH 16/17] create_symref(): do not assume pathname from git_path() persists long enough Being lazy to rely on the cycling N buffers mkpath() and friends return is nice in general, but it makes it too easy to introduce new bugs that are "mysterious". Introduction of read_ref() in create_symref() after calling git_path() to get the git_HEAD value (i.e. the path to create a new symref at) consumed more than the available buffers and broke a later call to mkpath() that derives lockpath from it. Signed-off-by: Junio C Hamano --- refs.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/refs.c b/refs.c index 591318501f..4a523086e3 100644 --- a/refs.c +++ b/refs.c @@ -992,7 +992,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master, const char *lockpath; char ref[1000]; int fd, len, written; - const char *git_HEAD = git_path("%s", ref_target); + char *git_HEAD = xstrdup(git_path("%s", ref_target)); unsigned char old_sha1[20], new_sha1[20]; if (logmsg && read_ref(ref_target, old_sha1)) @@ -1010,36 +1010,38 @@ int create_symref(const char *ref_target, const char *refs_heads_master, len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); if (sizeof(ref) <= len) { error("refname too long: %s", refs_heads_master); - return -1; + goto error_free_return; } lockpath = mkpath("%s.lock", git_HEAD); fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd < 0) { error("Unable to open %s for writing", lockpath); - return -5; + goto error_free_return; } written = write_in_full(fd, ref, len); close(fd); if (written != len) { - unlink(lockpath); error("Unable to write to %s", lockpath); - return -2; + goto error_unlink_return; } if (rename(lockpath, git_HEAD) < 0) { - unlink(lockpath); error("Unable to create %s", git_HEAD); - return -3; + goto error_unlink_return; } if (adjust_shared_perm(git_HEAD)) { - unlink(lockpath); error("Unable to fix permissions on %s", lockpath); - return -4; + error_unlink_return: + unlink(lockpath); + error_free_return: + free(git_HEAD); + return -1; } done: if (logmsg && !read_ref(refs_heads_master, new_sha1)) log_ref_write(ref_target, old_sha1, new_sha1, logmsg); + free(git_HEAD); return 0; } From a7e4fbf990290f955da1319ab605a59f85d3c3e2 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 26 Jan 2007 17:26:11 -0500 Subject: [PATCH 17/17] add reflog when moving HEAD to a new branch Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- git-checkout.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-checkout.sh b/git-checkout.sh index ac378cdb1e..1349e77f32 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -250,7 +250,7 @@ if [ "$?" -eq 0 ]; then fi if test -n "$branch" then - GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch" + GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch" elif test -n "$detached" then # NEEDSWORK: we would want a command to detach the HEAD