Merge branch 'lh/branch-rename'
authorJunio C Hamano <junkio@cox.net>
Wed, 13 Dec 2006 19:07:51 +0000 (11:07 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 13 Dec 2006 19:07:51 +0000 (11:07 -0800)
* lh/branch-rename:
git-branch: let caller specify logmsg
rename_ref: use lstat(2) when testing for symlink
git-branch: add options and tests for branch renaming

Conflicts:

builtin-branch.c

1  2 
builtin-branch.c
refs.c
diff --combined builtin-branch.c
index d1c243d372c09e1b664ae8e9b29398341767f8c8,3fc6f84773268379d355c69c0a32d4d9c4b0f630..560309cb154504a8d280f12e3d9d2d3d6d715d7c
@@@ -5,71 -5,18 +5,71 @@@
   * Based on git-branch.sh by Junio C Hamano.
   */
  
 +#include "color.h"
  #include "cache.h"
  #include "refs.h"
  #include "commit.h"
  #include "builtin.h"
  
  static const char builtin_branch_usage[] =
"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r | -a] [-v] [--abbrev=<length>] ";
  "git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [-r | -a] [-v [--abbrev=<length>]]";
  
  
  static const char *head;
  static unsigned char head_sha1[20];
  
 +static int branch_use_color;
 +static char branch_colors[][COLOR_MAXLEN] = {
 +      "\033[m",       /* reset */
 +      "",             /* PLAIN (normal) */
 +      "\033[31m",     /* REMOTE (red) */
 +      "",             /* LOCAL (normal) */
 +      "\033[32m",     /* CURRENT (green) */
 +};
 +enum color_branch {
 +      COLOR_BRANCH_RESET = 0,
 +      COLOR_BRANCH_PLAIN = 1,
 +      COLOR_BRANCH_REMOTE = 2,
 +      COLOR_BRANCH_LOCAL = 3,
 +      COLOR_BRANCH_CURRENT = 4,
 +};
 +
 +static int parse_branch_color_slot(const char *var, int ofs)
 +{
 +      if (!strcasecmp(var+ofs, "plain"))
 +              return COLOR_BRANCH_PLAIN;
 +      if (!strcasecmp(var+ofs, "reset"))
 +              return COLOR_BRANCH_RESET;
 +      if (!strcasecmp(var+ofs, "remote"))
 +              return COLOR_BRANCH_REMOTE;
 +      if (!strcasecmp(var+ofs, "local"))
 +              return COLOR_BRANCH_LOCAL;
 +      if (!strcasecmp(var+ofs, "current"))
 +              return COLOR_BRANCH_CURRENT;
 +      die("bad config variable '%s'", var);
 +}
 +
 +int git_branch_config(const char *var, const char *value)
 +{
 +      if (!strcmp(var, "color.branch")) {
 +              branch_use_color = git_config_colorbool(var, value);
 +              return 0;
 +      }
 +      if (!strncmp(var, "color.branch.", 13)) {
 +              int slot = parse_branch_color_slot(var, 13);
 +              color_parse(value, var, branch_colors[slot]);
 +              return 0;
 +      }
 +      return git_default_config(var, value);
 +}
 +
 +const char *branch_get_color(enum color_branch ix)
 +{
 +      if (branch_use_color)
 +              return branch_colors[ix];
 +      return "";
 +}
 +
  static int in_merge_bases(const unsigned char *sha1,
                          struct commit *rev1,
                          struct commit *rev2)
@@@ -236,7 -183,6 +236,7 @@@ static void print_ref_list(int kinds, i
        int i;
        char c;
        struct ref_list ref_list;
 +      int color;
  
        memset(&ref_list, 0, sizeof(ref_list));
        ref_list.kinds = kinds;
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
  
        for (i = 0; i < ref_list.index; i++) {
 +              switch( ref_list.list[i].kind ) {
 +                      case REF_LOCAL_BRANCH:
 +                              color = COLOR_BRANCH_LOCAL;
 +                              break;
 +                      case REF_REMOTE_BRANCH:
 +                              color = COLOR_BRANCH_REMOTE;
 +                              break;
 +                      default:
 +                              color = COLOR_BRANCH_PLAIN;
 +                              break;
 +              }
 +
                c = ' ';
                if (ref_list.list[i].kind == REF_LOCAL_BRANCH &&
 -                              !strcmp(ref_list.list[i].name, head))
 +                              !strcmp(ref_list.list[i].name, head)) {
                        c = '*';
 +                      color = COLOR_BRANCH_CURRENT;
 +              }
  
                if (verbose) {
 -                      printf("%c %-*s", c, ref_list.maxwidth,
 -                             ref_list.list[i].name);
 +                      printf("%c %s%-*s%s", c,
 +                                      branch_get_color(color),
 +                                      ref_list.maxwidth,
 +                                      ref_list.list[i].name,
 +                                      branch_get_color(COLOR_BRANCH_RESET));
                        print_ref_info(ref_list.list[i].sha1, abbrev);
                }
                else
 -                      printf("%c %s\n", c, ref_list.list[i].name);
 +                      printf("%c %s%s%s\n", c,
 +                                      branch_get_color(color),
 +                                      ref_list.list[i].name,
 +                                      branch_get_color(COLOR_BRANCH_RESET));
        }
  
        free_ref_list(&ref_list);
@@@ -319,15 -245,47 +319,47 @@@ static void create_branch(const char *n
                die("Failed to write ref: %s.", strerror(errno));
  }
  
+ static void rename_branch(const char *oldname, const char *newname, int force)
+ {
+       char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
+       unsigned char sha1[20];
+       if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref))
+               die("Old branchname too long");
+       if (check_ref_format(oldref))
+               die("Invalid branch name: %s", oldref);
+       if (snprintf(newref, sizeof(newref), "refs/heads/%s", newname) > sizeof(newref))
+               die("New branchname too long");
+       if (check_ref_format(newref))
+               die("Invalid branch name: %s", newref);
+       if (resolve_ref(newref, sha1, 1, NULL) && !force)
+               die("A branch named '%s' already exists.", newname);
+       snprintf(logmsg, sizeof(logmsg), "Branch: renamed %s to %s",
+                oldref, newref);
+       if (rename_ref(oldref, newref, logmsg))
+               die("Branch rename failed");
+       if (!strcmp(oldname, head) && create_symref("HEAD", newref))
+               die("Branch renamed to %s, but HEAD is not updated!", newname);
+ }
  int cmd_branch(int argc, const char **argv, const char *prefix)
  {
        int delete = 0, force_delete = 0, force_create = 0;
+       int rename = 0, force_rename = 0;
        int verbose = 0, abbrev = DEFAULT_ABBREV;
        int reflog = 0;
        int kinds = REF_LOCAL_BRANCH;
        int i;
  
 -      git_config(git_default_config);
+       setup_ident();
 +      git_config(git_branch_config);
  
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                        force_create = 1;
                        continue;
                }
+               if (!strcmp(arg, "-m")) {
+                       rename = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-M")) {
+                       rename = 1;
+                       force_rename = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-r")) {
                        kinds = REF_REMOTE_BRANCH;
                        continue;
                        verbose = 1;
                        continue;
                }
 +              if (!strcmp(arg, "--color")) {
 +                      branch_use_color = 1;
 +                      continue;
 +              }
 +              if (!strcmp(arg, "--no-color")) {
 +                      branch_use_color = 0;
 +                      continue;
 +              }
                usage(builtin_branch_usage);
        }
  
+       if ((delete && rename) || (delete && force_create) ||
+           (rename && force_create))
+               usage(builtin_branch_usage);
        head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
        if (!head)
                die("Failed to resolve HEAD as a valid ref.");
                delete_branches(argc - i, argv + i, force_delete);
        else if (i == argc)
                print_ref_list(kinds, verbose, abbrev);
+       else if (rename && (i == argc - 1))
+               rename_branch(head, argv[i], force_rename);
+       else if (rename && (i == argc - 2))
+               rename_branch(argv[i], argv[i + 1], force_rename);
        else if (i == argc - 1)
                create_branch(argv[i], head, force_create, reflog);
        else if (i == argc - 2)
diff --combined refs.c
index e56abb8585130a284b5f597cc8388c015b8e66d5,2ac8273ea475ebf169f86840587d48bf1e1b8a91..a02957c399ded94bb9a49c9dc3d8ab5d9411bbec
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -534,7 -534,7 +534,7 @@@ int check_ref_format(const char *ref
                level++;
                if (!ch) {
                        if (level < 2)
 -                              return -1; /* at least of form "heads/blah" */
 +                              return -2; /* at least of form "heads/blah" */
                        return 0;
                }
        }
@@@ -610,6 -610,29 +610,29 @@@ static int remove_empty_directories(cha
        return remove_empty_dir_recursive(path, len);
  }
  
+ static int is_refname_available(const char *ref, const char *oldref,
+                               struct ref_list *list, int quiet)
+ {
+       int namlen = strlen(ref); /* e.g. 'foo/bar' */
+       while (list) {
+               /* list->name could be 'foo' or 'foo/bar/baz' */
+               if (!oldref || strcmp(oldref, list->name)) {
+                       int len = strlen(list->name);
+                       int cmplen = (namlen < len) ? namlen : len;
+                       const char *lead = (namlen < len) ? list->name : ref;
+                       if (!strncmp(ref, list->name, cmplen) &&
+                           lead[cmplen] == '/') {
+                               if (!quiet)
+                                       error("'%s' exists; cannot create '%s'",
+                                             list->name, ref);
+                               return 0;
+                       }
+               }
+               list = list->next;
+       }
+       return 1;
+ }
  static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
  {
        char *ref_file;
                        orig_ref, strerror(errno));
                goto error_return;
        }
-       if (is_null_sha1(lock->old_sha1)) {
-               /* The ref did not exist and we are creating it.
-                * Make sure there is no existing ref that is packed
-                * whose name begins with our refname, nor a ref whose
-                * name is a proper prefix of our refname.
-                */
-               int namlen = strlen(ref); /* e.g. 'foo/bar' */
-               struct ref_list *list = get_packed_refs();
-               while (list) {
-                       /* list->name could be 'foo' or 'foo/bar/baz' */
-                       int len = strlen(list->name);
-                       int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? list->name : ref;
-                       if (!strncmp(ref, list->name, cmplen) &&
-                           lead[cmplen] == '/') {
-                               error("'%s' exists; cannot create '%s'",
-                                     list->name, ref);
-                               goto error_return;
-                       }
-                       list = list->next;
-               }
-       }
+       /* When the ref did not exist and we are creating it,
+        * make sure there is no existing ref that is packed
+        * whose name begins with our refname, nor a ref whose
+        * name is a proper prefix of our refname.
+        */
+       if (is_null_sha1(lock->old_sha1) &&
+             !is_refname_available(ref, NULL, get_packed_refs(), 0))
+               goto error_return;
  
        lock->lk = xcalloc(1, sizeof(struct lock_file));
  
@@@ -776,6 -784,117 +784,117 @@@ int delete_ref(const char *refname, uns
        return ret;
  }
  
+ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+ {
+       static const char renamed_ref[] = "RENAMED-REF";
+       unsigned char sha1[20], orig_sha1[20];
+       int flag = 0, logmoved = 0;
+       struct ref_lock *lock;
+       struct stat loginfo;
+       int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+       if (S_ISLNK(loginfo.st_mode))
+               return error("reflog for %s is a symlink", oldref);
+       if (!resolve_ref(oldref, orig_sha1, 1, &flag))
+               return error("refname %s not found", oldref);
+       if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
+               return 1;
+       if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
+               return 1;
+       lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL);
+       if (!lock)
+               return error("unable to lock %s", renamed_ref);
+       lock->force_write = 1;
+       if (write_ref_sha1(lock, orig_sha1, logmsg))
+               return error("unable to save current sha1 in %s", renamed_ref);
+       if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log")))
+               return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+       if (delete_ref(oldref, orig_sha1)) {
+               error("unable to delete old %s", oldref);
+               goto rollback;
+       }
+       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) {
+               if (errno==EISDIR) {
+                       if (remove_empty_directories(git_path("%s", newref))) {
+                               error("Directory not empty: %s", newref);
+                               goto rollback;
+                       }
+               } else {
+                       error("unable to delete existing %s", newref);
+                       goto rollback;
+               }
+       }
+       if (log && safe_create_leading_directories(git_path("logs/%s", newref))) {
+               error("unable to create directory for %s", newref);
+               goto rollback;
+       }
+  retry:
+       if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
+               if (errno==EISDIR) {
+                       if (remove_empty_directories(git_path("logs/%s", newref))) {
+                               error("Directory not empty: logs/%s", newref);
+                               goto rollback;
+                       }
+                       goto retry;
+               } else {
+                       error("unable to move logfile tmp-renamed-log to logs/%s: %s",
+                               newref, strerror(errno));
+                       goto rollback;
+               }
+       }
+       logmoved = log;
+       lock = lock_ref_sha1_basic(newref, NULL, NULL);
+       if (!lock) {
+               error("unable to lock %s for update", newref);
+               goto rollback;
+       }
+       lock->force_write = 1;
+       hashcpy(lock->old_sha1, orig_sha1);
+       if (write_ref_sha1(lock, orig_sha1, logmsg)) {
+               error("unable to write current sha1 into %s", newref);
+               goto rollback;
+       }
+       return 0;
+  rollback:
+       lock = lock_ref_sha1_basic(oldref, NULL, NULL);
+       if (!lock) {
+               error("unable to lock %s for rollback", oldref);
+               goto rollbacklog;
+       }
+       lock->force_write = 1;
+       flag = log_all_ref_updates;
+       log_all_ref_updates = 0;
+       if (write_ref_sha1(lock, orig_sha1, NULL))
+               error("unable to write current sha1 into %s", oldref);
+       log_all_ref_updates = flag;
+  rollbacklog:
+       if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from %s: %s",
+                       oldref, newref, strerror(errno));
+       if (!logmoved && log &&
+           rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+       return 1;
+ }
  void unlock_ref(struct ref_lock *lock)
  {
        if (lock->lock_fd >= 0) {