Merge branch 'js/branch-track'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Feb 2008 21:02:57 +0000 (13:02 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Feb 2008 21:02:57 +0000 (13:02 -0800)
* js/branch-track:
  doc: documentation update for the branch track changes
  branch: optionally setup branch.*.merge from upstream local branches

Conflicts:

Documentation/config.txt
Documentation/git-branch.txt
Documentation/git-checkout.txt
builtin-branch.c
cache.h
t/t7201-co.sh

1  2 
Documentation/config.txt
builtin-branch.c
builtin-checkout.c
cache.h
config.c
environment.c
t/t3200-branch.sh
t/t7201-co.sh

diff --combined Documentation/config.txt
@@@ -139,51 -139,6 +139,51 @@@ core.autocrlf:
        "text" (i.e. be subjected to the autocrlf mechanism) is
        decided purely based on the contents.
  
 +core.safecrlf::
 +      If true, makes git check if converting `CRLF` as controlled by
 +      `core.autocrlf` is reversible.  Git will verify if a command
 +      modifies a file in the work tree either directly or indirectly.
 +      For example, committing a file followed by checking out the
 +      same file should yield the original file in the work tree.  If
 +      this is not the case for the current setting of
 +      `core.autocrlf`, git will reject the file.  The variable can
 +      be set to "warn", in which case git will only warn about an
 +      irreversible conversion but continue the operation.
 ++
 +CRLF conversion bears a slight chance of corrupting data.
 +autocrlf=true will convert CRLF to LF during commit and LF to
 +CRLF during checkout.  A file that contains a mixture of LF and
 +CRLF before the commit cannot be recreated by git.  For text
 +files this is the right thing to do: it corrects line endings
 +such that we have only LF line endings in the repository.
 +But for binary files that are accidentally classified as text the
 +conversion can corrupt data.
 ++
 +If you recognize such corruption early you can easily fix it by
 +setting the conversion type explicitly in .gitattributes.  Right
 +after committing you still have the original file in your work
 +tree and this file is not yet corrupted.  You can explicitly tell
 +git that this file is binary and git will handle the file
 +appropriately.
 ++
 +Unfortunately, the desired effect of cleaning up text files with
 +mixed line endings and the undesired effect of corrupting binary
 +files cannot be distinguished.  In both cases CRLFs are removed
 +in an irreversible way.  For text files this is the right thing
 +to do because CRLFs are line endings, while for binary files
 +converting CRLFs corrupts data.
 ++
 +Note, this safety check does not mean that a checkout will generate a
 +file identical to the original file for a different setting of
 +`core.autocrlf`, but only for the current one.  For example, a text
 +file with `LF` would be accepted with `core.autocrlf=input` and could
 +later be checked out with `core.autocrlf=true`, in which case the
 +resulting file would contain `CRLF`, although the original file
 +contained `LF`.  However, in both work trees the line endings would be
 +consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
 +file with mixed line endings would be reported by the `core.safecrlf`
 +mechanism.
 +
  core.symlinks::
        If false, symbolic links are checked out as small plain files that
        contain the link text. linkgit:git-update-index[1] and
@@@ -353,10 -308,6 +353,10 @@@ core.whitespace:
    error (enabled by default).
  * `indent-with-non-tab` treats a line that is indented with 8 or more
    space characters as an error (not enabled by default).
 +* `cr-at-eol` treats a carriage-return at the end of line as
 +  part of the line terminator, i.e. with it, `trailing-space`
 +  does not trigger if the character before such a carriage-return
 +  is not a whitespace (not enabled by default).
  
  alias.*::
        Command aliases for the linkgit:git[1] command wrapper - e.g.
@@@ -379,10 -330,14 +379,14 @@@ apply.whitespace:
  
  branch.autosetupmerge::
        Tells `git-branch` and `git-checkout` to setup new branches
-       so that linkgit:git-pull[1] will appropriately merge from that
-       remote branch.  Note that even if this option is not set,
+       so that linkgit:git-pull[1] will appropriately merge from the
+       starting point branch. Note that even if this option is not set,
        this behavior can be chosen per-branch using the `--track`
-       and `--no-track` options.  This option defaults to true.
+       and `--no-track` options. The valid settings are: `false` -- no
+       automatic setup is done; `true` -- automatic setup is done when the
+       starting point is a remote branch; `always` -- automatic setup is
+       done when the starting point is either a local branch or remote
+       branch. This option defaults to true.
  
  branch.<name>.remote::
        When in branch <name>, it tells `git fetch` which remote to fetch.
@@@ -416,11 -371,6 +420,11 @@@ branch.<name>.rebase:
        it unless you understand the implications (see linkgit:git-rebase[1]
        for details).
  
 +browser.<tool>.path::
 +      Override the path for the given tool that may be used to
 +      browse HTML help (see '-w' option in linkgit:git-help[1]) or a
 +      working repository in gitweb (see linkgit:git-instaweb[1]).
 +
  clean.requireForce::
        A boolean to make git-clean do nothing unless given -f
        or -n.   Defaults to true.
@@@ -493,13 -443,6 +497,13 @@@ color.status.<slot>:
  commit.template::
        Specify a file to use as the template for new commit messages.
  
 +color.ui::
 +      When set to `always`, always use colors in all git commands which
 +      are capable of colored output. When false (or `never`), never. When
 +      set to `true` or `auto`, use colors only when the output is to the
 +      terminal. When more specific variables of color.* are set, they always
 +      take precedence over this setting. Defaults to false.
 +
  diff.autorefreshindex::
        When using `git diff` to compare with work tree
        files, do not consider stat-only change as changed.
@@@ -812,8 -755,6 +816,8 @@@ pack.threads:
        warning. This is meant to reduce packing time on multiprocessor
        machines. The required amount of memory for the delta search window
        is however multiplied by the number of threads.
 +      Specifying 0 will cause git to auto-detect the number of CPU's
 +      and set the number of threads accordingly.
  
  pack.indexVersion::
        Specify the default pack index version.  Valid values are 1 for
        whenever the corresponding pack is larger than 2 GB.  Otherwise
        the default is 1.
  
 +pack.packSizeLimit:
 +      The default maximum size of a pack.  This setting only affects
 +      packing to a file, i.e. the git:// protocol is unaffected.  It
 +      can be overridden by the `\--max-pack-size` option of
 +      linkgit:git-repack[1].
 +
  pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@@ -899,17 -834,6 +903,17 @@@ tar.umask:
        archiving user's umask will be used instead.  See umask(2) and
        linkgit:git-archive[1].
  
 +url.<base>.insteadOf::
 +      Any URL that starts with this value will be rewritten to
 +      start, instead, with <base>. In cases where some site serves a
 +      large number of repositories, and serves them with multiple
 +      access methods, and some users need to use different access
 +      methods, this feature allows people to specify any of the
 +      equivalent URLs and have git automatically rewrite the URL to
 +      the best alternative for the particular user, even for a
 +      never-before-seen repository on the site.  When more than one
 +      insteadOf strings match a given URL, the longest match is used.
 +
  user.email::
        Your email address to be recorded in any newly created commits.
        Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
diff --combined builtin-branch.c
@@@ -30,9 -30,7 +30,7 @@@ static const char * const builtin_branc
  static const char *head;
  static unsigned char head_sha1[20];
  
- static int branch_track = 1;
 -static int branch_use_color;
 +static int branch_use_color = -1;
  static char branch_colors[][COLOR_MAXLEN] = {
        "\033[m",       /* reset */
        "",             /* PLAIN (normal) */
@@@ -71,21 -69,15 +69,17 @@@ static int git_branch_config(const cha
        }
        if (!prefixcmp(var, "color.branch.")) {
                int slot = parse_branch_color_slot(var, 13);
 +              if (!value)
 +                      return config_error_nonbool(var);
                color_parse(value, var, branch_colors[slot]);
                return 0;
        }
-       if (!strcmp(var, "branch.autosetupmerge")) {
-               branch_track = git_config_bool(var, value);
-               return 0;
-       }
 -      return git_default_config(var, value);
 +      return git_color_default_config(var, value);
  }
  
  static const char *branch_get_color(enum color_branch ix)
  {
 -      if (branch_use_color)
 +      if (branch_use_color > 0)
                return branch_colors[ix];
        return "";
  }
@@@ -420,14 -412,16 +414,16 @@@ int cmd_branch(int argc, const char **a
  {
        int delete = 0, rename = 0, force_create = 0;
        int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
-       int reflog = 0, track;
+       int reflog = 0;
+       enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
        struct commit_list *with_commit = NULL;
  
        struct option options[] = {
                OPT_GROUP("Generic options"),
                OPT__VERBOSE(&verbose),
-               OPT_BOOLEAN( 0 , "track",  &track, "set up tracking mode (see git-pull(1))"),
+               OPT_SET_INT( 0 , "track",  &track, "set up tracking mode (see git-pull(1))",
+                       BRANCH_TRACK_EXPLICIT),
                OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
                OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
        };
  
        git_config(git_branch_config);
-       track = branch_track;
 +
 +      if (branch_use_color == -1)
 +              branch_use_color = git_use_color_default;
 +
+       track = git_branch_track;
        argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
        if (!!delete + !!rename + !!force_create > 1)
                usage_with_options(builtin_branch_usage, options);
diff --combined builtin-checkout.c
@@@ -186,7 -186,7 +186,7 @@@ struct checkout_opts 
  
        char *new_branch;
        int new_branch_log;
-       int track;
+       enum branch_track track;
  };
  
  struct branch_info {
@@@ -205,7 -205,8 +205,7 @@@ static void setup_branch_path(struct br
  }
  
  static int merge_working_tree(struct checkout_opts *opts,
 -                            struct branch_info *old, struct branch_info *new,
 -                            const char *prefix)
 +                            struct branch_info *old, struct branch_info *new)
  {
        int ret;
        struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
                refresh_cache(REFRESH_QUIET);
  
                if (unmerged_cache()) {
 -                      ret = opts->merge ? -1 :
 -                              error("you need to resolve your current index first");
 -              } else {
 -                      topts.update = 1;
 -                      topts.merge = 1;
 -                      topts.gently = opts->merge;
 -                      topts.fn = twoway_merge;
 -                      topts.dir = xcalloc(1, sizeof(*topts.dir));
 -                      topts.dir->show_ignored = 1;
 -                      topts.dir->exclude_per_dir = ".gitignore";
 -                      topts.prefix = prefix;
 -                      tree = parse_tree_indirect(old->commit->object.sha1);
 -                      init_tree_desc(&trees[0], tree->buffer, tree->size);
 -                      tree = parse_tree_indirect(new->commit->object.sha1);
 -                      init_tree_desc(&trees[1], tree->buffer, tree->size);
 -                      ret = unpack_trees(2, trees, &topts);
 +                      error("you need to resolve your current index first");
 +                      return 1;
                }
 -              if (ret) {
 +
 +              /* 2-way merge to the new branch */
 +              topts.update = 1;
 +              topts.merge = 1;
 +              topts.gently = opts->merge;
 +              topts.verbose_update = !opts->quiet;
 +              topts.fn = twoway_merge;
 +              topts.dir = xcalloc(1, sizeof(*topts.dir));
 +              topts.dir->show_ignored = 1;
 +              topts.dir->exclude_per_dir = ".gitignore";
 +              tree = parse_tree_indirect(old->commit->object.sha1);
 +              init_tree_desc(&trees[0], tree->buffer, tree->size);
 +              tree = parse_tree_indirect(new->commit->object.sha1);
 +              init_tree_desc(&trees[1], tree->buffer, tree->size);
 +
 +              if (unpack_trees(2, trees, &topts)) {
                        /*
                         * Unpack couldn't do a trivial merge; either
                         * give up or do a real merge, depending on
        return 0;
  }
  
 -static void adjust_to_tracking(struct branch_info *new, struct checkout_opts *opts)
 +static void report_tracking(struct branch_info *new, struct checkout_opts *opts)
  {
        /*
         * We have switched to a new branch; is it building on
        char *base;
        unsigned char sha1[20];
        struct commit *ours, *theirs;
 -      const char *msgfmt;
        char symmetric[84];
 -      int show_log;
 -      struct branch *branch = branch_get(NULL);
 +      struct rev_info revs;
 +      const char *rev_argv[10];
 +      int rev_argc;
 +      int num_ours, num_theirs;
 +      const char *remote_msg;
 +      struct branch *branch = branch_get(new->name);
  
 -      if (!branch || !branch->merge)
 +      /*
 +       * Nothing to report unless we are marked to build on top of
 +       * somebody else.
 +       */
 +      if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
                return;
  
 -      base = branch->merge[0]->dst;
 -
 -      ours = new->commit;
 -
 -      sprintf(symmetric, "%s", sha1_to_hex(ours->object.sha1));
 -
        /*
 -       * Ok, it is tracking base; is it ahead of us?
 +       * If what we used to build on no longer exists, there is
 +       * nothing to report.
         */
 +      base = branch->merge[0]->dst;
        if (!resolve_ref(base, sha1, 1, NULL))
                return;
 -      theirs = lookup_commit(sha1);
 -
 -      sprintf(symmetric + 40, "...%s", sha1_to_hex(sha1));
  
 +      theirs = lookup_commit(sha1);
 +      ours = new->commit;
        if (!hashcmp(sha1, ours->object.sha1))
                return; /* we are the same */
  
 -      show_log = 1;
 -      if (in_merge_bases(theirs, &ours, 1)) {
 -              msgfmt = "You are ahead of the tracked branch '%s'\n";
 -              show_log = 0;
 +      /* Run "rev-list --left-right ours...theirs" internally... */
 +      rev_argc = 0;
 +      rev_argv[rev_argc++] = NULL;
 +      rev_argv[rev_argc++] = "--left-right";
 +      rev_argv[rev_argc++] = symmetric;
 +      rev_argv[rev_argc++] = "--";
 +      rev_argv[rev_argc] = NULL;
 +
 +      strcpy(symmetric, sha1_to_hex(ours->object.sha1));
 +      strcpy(symmetric + 40, "...");
 +      strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
 +
 +      init_revisions(&revs, NULL);
 +      setup_revisions(rev_argc, rev_argv, &revs, NULL);
 +      prepare_revision_walk(&revs);
 +
 +      /* ... and count the commits on each side. */
 +      num_ours = 0;
 +      num_theirs = 0;
 +      while (1) {
 +              struct commit *c = get_revision(&revs);
 +              if (!c)
 +                      break;
 +              if (c->object.flags & SYMMETRIC_LEFT)
 +                      num_ours++;
 +              else
 +                      num_theirs++;
        }
 -      else if (in_merge_bases(ours, &theirs, 1))
 -              msgfmt = "Your branch can be fast-forwarded to the tracked branch '%s'\n";
 -      else
 -              msgfmt = "Both your branch and the tracked branch '%s' have own changes, you would eventually need to merge\n";
  
 -      if (!prefixcmp(base, "refs/remotes/"))
 +      if (!prefixcmp(base, "refs/remotes/")) {
 +              remote_msg = " remote";
                base += strlen("refs/remotes/");
 -      fprintf(stderr, msgfmt, base);
 -
 -      if (show_log) {
 -              const char *args[32];
 -              int ac;
 -
 -              ac = 0;
 -              args[ac++] = "log";
 -              args[ac++] = "--pretty=oneline";
 -              args[ac++] = "--abbrev-commit";
 -              args[ac++] = "--left-right";
 -              args[ac++] = "--boundary";
 -              args[ac++] = symmetric;
 -              args[ac++] = "--";
 -              args[ac] = NULL;
 -
 -              run_command_v_opt(args, RUN_GIT_CMD);
 +      } else {
 +              remote_msg = "";
        }
 -}
  
 +      if (!num_theirs)
 +              printf("Your branch is ahead of the tracked%s branch '%s' "
 +                     "by %d commit%s.\n",
 +                     remote_msg, base,
 +                     num_ours, (num_ours == 1) ? "" : "s");
 +      else if (!num_ours)
 +              printf("Your branch is behind the tracked%s branch '%s' "
 +                     "by %d commit%s,\n"
 +                     "and can be fast-forwarded.\n",
 +                     remote_msg, base,
 +                     num_theirs, (num_theirs == 1) ? "" : "s");
 +      else
 +              printf("Your branch and the tracked%s branch '%s' "
 +                     "have diverged,\nand respectively "
 +                     "have %d and %d different commit(s) each.\n",
 +                     remote_msg, base,
 +                     num_ours, num_theirs);
 +}
  
  static void update_refs_for_switch(struct checkout_opts *opts,
                                   struct branch_info *old,
        }
        remove_branch_state();
        strbuf_release(&msg);
 -      if (new->path || !strcmp(new->name, "HEAD"))
 -              adjust_to_tracking(new, opts);
 +      if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
 +              report_tracking(new, opts);
  }
  
 -static int switch_branches(struct checkout_opts *opts,
 -                         struct branch_info *new, const char *prefix)
 +static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
  {
        int ret = 0;
        struct branch_info old;
                opts->force = 1;
        }
  
 -      ret = merge_working_tree(opts, &old, new, prefix);
 +      ret = merge_working_tree(opts, &old, new);
        if (ret)
                return ret;
  
        return post_checkout_hook(old.commit, new->commit, 1);
  }
  
- static int branch_track = 0;
  static int git_checkout_config(const char *var, const char *value)
  {
-       if (!strcmp(var, "branch.autosetupmerge"))
-               branch_track = git_config_bool(var, value);
        return git_default_config(var, value);
  }
  
@@@ -501,7 -473,8 +496,8 @@@ int cmd_checkout(int argc, const char *
                OPT__QUIET(&opts.quiet),
                OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
                OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
-               OPT_BOOLEAN( 0 , "track", &opts.track, "track"),
+               OPT_SET_INT( 0 , "track",  &opts.track, "track",
+                       BRANCH_TRACK_EXPLICIT),
                OPT_BOOLEAN('f', NULL, &opts.force, "force"),
                OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
                OPT_END(),
  
        git_config(git_checkout_config);
  
-       opts.track = branch_track;
+       opts.track = git_branch_track;
  
        argc = parse_options(argc, argv, options, checkout_usage, 0);
        if (argc) {
                argc--;
        }
  
-       if (!opts.new_branch && (opts.track != branch_track))
+       if (!opts.new_branch && (opts.track != git_branch_track))
                die("git checkout: --track and --no-track require -b");
  
        if (opts.force && opts.merge)
                die("Cannot switch branch to a non-commit.");
        }
  
 -      return switch_branches(&opts, &new, prefix);
 +      return switch_branches(&opts, &new);
  }
diff --combined cache.h
+++ b/cache.h
@@@ -110,6 -110,7 +110,6 @@@ struct ondisk_cache_entry 
  };
  
  struct cache_entry {
 -      struct cache_entry *next;
        unsigned int ce_ctime;
        unsigned int ce_mtime;
        unsigned int ce_dev;
        unsigned int ce_size;
        unsigned int ce_flags;
        unsigned char sha1[20];
 +      struct cache_entry *next;
        char name[FLEX_ARRAY]; /* more */
  };
  
  #define CE_UPDATE    (0x10000)
  #define CE_REMOVE    (0x20000)
  #define CE_UPTODATE  (0x40000)
 -#define CE_UNHASHED  (0x80000)
 +
 +#define CE_HASHED    (0x100000)
 +#define CE_UNHASHED  (0x200000)
 +
 +/*
 + * Copy the sha1 and stat state of a cache entry from one to
 + * another. But we never change the name, or the hash state!
 + */
 +#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 +static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +{
 +      unsigned int state = dst->ce_flags & CE_STATE_MASK;
 +
 +      /* Don't copy hash chain and name */
 +      memcpy(dst, src, offsetof(struct cache_entry, next));
 +
 +      /* Restore the hash state */
 +      dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
 +}
 +
 +/*
 + * We don't actually *remove* it, we can just mark it invalid so that
 + * we won't find it in lookups.
 + *
 + * Not only would we have to search the lists (simple enough), but
 + * we'd also have to rehash other hash buckets in case this makes the
 + * hash bucket empty (common). So it's much better to just mark
 + * it.
 + */
 +static inline void remove_index_entry(struct cache_entry *ce)
 +{
 +      ce->ce_flags |= CE_UNHASHED;
 +}
  
  static inline unsigned create_ce_flags(size_t len, unsigned stage)
  {
@@@ -210,18 -178,6 +210,18 @@@ static inline unsigned int ce_mode_from
        }
        return create_ce_mode(mode);
  }
 +static inline int ce_to_dtype(const struct cache_entry *ce)
 +{
 +      unsigned ce_mode = ntohl(ce->ce_mode);
 +      if (S_ISREG(ce_mode))
 +              return DT_REG;
 +      else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))
 +              return DT_DIR;
 +      else if (S_ISLNK(ce_mode))
 +              return DT_LNK;
 +      else
 +              return DT_UNKNOWN;
 +}
  #define canon_mode(mode) \
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
        S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
@@@ -417,14 -373,15 +417,23 @@@ extern size_t packed_git_limit
  extern size_t delta_base_cache_limit;
  extern int auto_crlf;
  
 +enum safe_crlf {
 +      SAFE_CRLF_FALSE = 0,
 +      SAFE_CRLF_FAIL = 1,
 +      SAFE_CRLF_WARN = 2,
 +};
 +
 +extern enum safe_crlf safe_crlf;
 +
+ enum branch_track {
+       BRANCH_TRACK_NEVER = 0,
+       BRANCH_TRACK_REMOTE,
+       BRANCH_TRACK_ALWAYS,
+       BRANCH_TRACK_EXPLICIT,
+ };
+ extern enum branch_track git_branch_track;
  #define GIT_REPO_VERSION 0
  extern int repository_format_version;
  extern int check_repository_format(void);
@@@ -679,16 -636,11 +688,16 @@@ extern int git_parse_ulong(const char *
  extern int git_config_int(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
  extern int git_config_bool(const char *, const char *);
 +extern int git_config_string(const char **, const char *, const char *);
  extern int git_config_set(const char *, const char *);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
  extern const char *git_etc_gitconfig(void);
  extern int check_repository_format_version(const char *var, const char *value);
 +extern int git_env_bool(const char *, int);
 +extern int git_config_system(void);
 +extern int git_config_global(void);
 +extern int config_error_nonbool(const char *);
  
  #define MAX_GITNAME (1000)
  extern char git_default_email[MAX_GITNAME];
@@@ -700,7 -652,6 +709,7 @@@ extern const char *git_log_output_encod
  /* IO helper functions */
  extern void maybe_flush_or_die(FILE *, const char *);
  extern int copy_fd(int ifd, int ofd);
 +extern int copy_file(const char *dst, const char *src, int mode);
  extern int read_in_full(int fd, void *buf, size_t count);
  extern int write_in_full(int fd, const void *buf, size_t count);
  extern void write_or_die(int fd, const void *buf, size_t count);
@@@ -709,12 -660,12 +718,12 @@@ extern int write_or_whine_pipe(int fd, 
  
  /* pager.c */
  extern void setup_pager(void);
 -extern char *pager_program;
 +extern const char *pager_program;
  extern int pager_in_use(void);
  extern int pager_use_color;
  
 -extern char *editor_program;
 -extern char *excludes_file;
 +extern const char *editor_program;
 +extern const char *excludes_file;
  
  /* base85 */
  int decode_85(char *dst, const char *line, int linelen);
@@@ -734,8 -685,7 +743,8 @@@ extern void trace_argv_printf(const cha
  
  /* convert.c */
  /* returns 1 if *dst was used */
 -extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst);
 +extern int convert_to_git(const char *path, const char *src, size_t len,
 +                          struct strbuf *dst, enum safe_crlf checksafe);
  extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
  
  /* add */
@@@ -754,7 -704,6 +763,7 @@@ void shift_tree(const unsigned char *, 
  #define WS_TRAILING_SPACE     01
  #define WS_SPACE_BEFORE_TAB   02
  #define WS_INDENT_WITH_NON_TAB        04
 +#define WS_CR_AT_EOL           010
  #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
  extern unsigned whitespace_rule_cfg;
  extern unsigned whitespace_rule(const char *);
@@@ -763,13 -712,10 +772,13 @@@ extern unsigned check_and_emit_line(con
      FILE *stream, const char *set,
      const char *reset, const char *ws);
  extern char *whitespace_error_string(unsigned ws);
 +extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
  
  /* ls-files */
  int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
  int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
 +char *alias_lookup(const char *alias);
 +
  #endif /* CACHE_H */
diff --combined config.c
+++ b/config.c
@@@ -280,18 -280,11 +280,18 @@@ int git_parse_ulong(const char *value, 
        return 0;
  }
  
 +static void die_bad_config(const char *name)
 +{
 +      if (config_file_name)
 +              die("bad config value for '%s' in %s", name, config_file_name);
 +      die("bad config value for '%s'", name);
 +}
 +
  int git_config_int(const char *name, const char *value)
  {
        long ret;
        if (!git_parse_long(value, &ret))
 -              die("bad config value for '%s' in %s", name, config_file_name);
 +              die_bad_config(name);
        return ret;
  }
  
@@@ -299,7 -292,7 +299,7 @@@ unsigned long git_config_ulong(const ch
  {
        unsigned long ret;
        if (!git_parse_ulong(value, &ret))
 -              die("bad config value for '%s' in %s", name, config_file_name);
 +              die_bad_config(name);
        return ret;
  }
  
@@@ -316,14 -309,6 +316,14 @@@ int git_config_bool(const char *name, c
        return git_config_int(name, value) != 0;
  }
  
 +int git_config_string(const char **dest, const char *var, const char *value)
 +{
 +      if (!value)
 +              return config_error_nonbool(var);
 +      *dest = xstrdup(value);
 +      return 0;
 +}
 +
  int git_default_config(const char *var, const char *value)
  {
        /* This needs a better name */
                return 0;
        }
  
 +      if (!strcmp(var, "core.safecrlf")) {
 +              if (value && !strcasecmp(value, "warn")) {
 +                      safe_crlf = SAFE_CRLF_WARN;
 +                      return 0;
 +              }
 +              safe_crlf = git_config_bool(var, value);
 +              return 0;
 +      }
 +
        if (!strcmp(var, "user.name")) {
 +              if (!value)
 +                      return config_error_nonbool(var);
                strlcpy(git_default_name, value, sizeof(git_default_name));
                return 0;
        }
  
        if (!strcmp(var, "user.email")) {
 +              if (!value)
 +                      return config_error_nonbool(var);
                strlcpy(git_default_email, value, sizeof(git_default_email));
                return 0;
        }
  
 -      if (!strcmp(var, "i18n.commitencoding")) {
 -              git_commit_encoding = xstrdup(value);
 -              return 0;
 -      }
 -
 -      if (!strcmp(var, "i18n.logoutputencoding")) {
 -              git_log_output_encoding = xstrdup(value);
 -              return 0;
 -      }
 +      if (!strcmp(var, "i18n.commitencoding"))
 +              return git_config_string(&git_commit_encoding, var, value);
  
 +      if (!strcmp(var, "i18n.logoutputencoding"))
 +              return git_config_string(&git_log_output_encoding, var, value);
  
        if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
        }
  
 -      if (!strcmp(var, "core.pager")) {
 -              pager_program = xstrdup(value);
 -              return 0;
 -      }
 +      if (!strcmp(var, "core.pager"))
 +              return git_config_string(&pager_program, var, value);
  
 -      if (!strcmp(var, "core.editor")) {
 -              editor_program = xstrdup(value);
 -              return 0;
 -      }
 +      if (!strcmp(var, "core.editor"))
 +              return git_config_string(&editor_program, var, value);
  
 -      if (!strcmp(var, "core.excludesfile")) {
 -              if (!value)
 -                      die("core.excludesfile without value");
 -              excludes_file = xstrdup(value);
 -              return 0;
 -      }
 +      if (!strcmp(var, "core.excludesfile"))
 +              return git_config_string(&excludes_file, var, value);
  
        if (!strcmp(var, "core.whitespace")) {
 +              if (!value)
 +                      return config_error_nonbool(var);
                whitespace_rule_cfg = parse_whitespace_rule(value);
                return 0;
        }
+       if (!strcmp(var, "branch.autosetupmerge")) {
+               if (value && !strcasecmp(value, "always")) {
+                       git_branch_track = BRANCH_TRACK_ALWAYS;
+                       return 0;
+               }
+               git_branch_track = git_config_bool(var, value);
+               return 0;
+       }
  
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
@@@ -501,30 -492,14 +509,30 @@@ const char *git_etc_gitconfig(void
                system_wide = ETC_GITCONFIG;
                if (!is_absolute_path(system_wide)) {
                        /* interpret path relative to exec-dir */
 -                      const char *exec_path = git_exec_path();
 -                      system_wide = prefix_path(exec_path, strlen(exec_path),
 -                                              system_wide);
 +                      struct strbuf d = STRBUF_INIT;
 +                      strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
 +                      system_wide = strbuf_detach(&d, NULL);
                }
        }
        return system_wide;
  }
  
 +int git_env_bool(const char *k, int def)
 +{
 +      const char *v = getenv(k);
 +      return v ? git_config_bool(k, v) : def;
 +}
 +
 +int git_config_system(void)
 +{
 +      return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 +}
 +
 +int git_config_global(void)
 +{
 +      return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0);
 +}
 +
  int git_config(config_fn_t fn)
  {
        int ret = 0;
         * config file otherwise. */
        filename = getenv(CONFIG_ENVIRONMENT);
        if (!filename) {
 -              if (!access(git_etc_gitconfig(), R_OK))
 +              if (git_config_system() && !access(git_etc_gitconfig(), R_OK))
                        ret += git_config_from_file(fn, git_etc_gitconfig());
                home = getenv("HOME");
                filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                        filename = repo_config = xstrdup(git_path("config"));
        }
  
 -      if (home) {
 +      if (git_config_global() && home) {
                char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
                if (!access(user_config, R_OK))
                        ret = git_config_from_file(fn, user_config);
@@@ -734,17 -709,12 +742,17 @@@ static ssize_t find_beginning_of_line(c
        size_t equal_offset = size, bracket_offset = size;
        ssize_t offset;
  
 +contline:
        for (offset = offset_-2; offset > 0
                        && contents[offset] != '\n'; offset--)
                switch (contents[offset]) {
                        case '=': equal_offset = offset; break;
                        case ']': bracket_offset = offset; break;
                }
 +      if (offset > 0 && contents[offset-1] == '\\') {
 +              offset_ = offset;
 +              goto contline;
 +      }
        if (bracket_offset < equal_offset) {
                *found_bracket = 1;
                offset = bracket_offset+1;
@@@ -1112,12 -1082,3 +1120,12 @@@ int git_config_rename_section(const cha
        free(config_filename);
        return ret;
  }
 +
 +/*
 + * Call this to report error for your variable that should not
 + * get a boolean value (i.e. "[my] var" means "true").
 + */
 +int config_error_nonbool(const char *var)
 +{
 +      return error("Missing value for '%s'", var);
 +}
diff --combined environment.c
@@@ -30,13 -30,13 +30,14 @@@ int core_compression_seen
  size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
  size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
  size_t delta_base_cache_limit = 16 * 1024 * 1024;
 -char *pager_program;
 +const char *pager_program;
  int pager_use_color = 1;
 -char *editor_program;
 -char *excludes_file;
 +const char *editor_program;
 +const char *excludes_file;
  int auto_crlf = 0;    /* 1: both ways, -1: only when adding git objects */
 +enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
  unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
+ enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
  
  /* This is set by setup_git_dir_gently() and/or git_default_config() */
  char *git_work_tree_cfg;
diff --combined t/t3200-branch.sh
@@@ -15,13 -15,15 +15,16 @@@ test_expect_success 
      'echo Hello > A &&
       git update-index --add A &&
       git-commit -m "Initial commit." &&
+      echo World >> A &&
+      git update-index --add A &&
+      git-commit -m "Second commit." &&
       HEAD=$(git rev-parse --verify HEAD)'
  
 -test_expect_failure \
 -    'git branch --help should not have created a bogus branch' \
 -    'git branch --help </dev/null >/dev/null 2>/dev/null || :
 -     test -f .git/refs/heads/--help'
 +test_expect_success \
 +    'git branch --help should not have created a bogus branch' '
 +     git branch --help </dev/null >/dev/null 2>/dev/null;
 +     ! test -f .git/refs/heads/--help
 +'
  
  test_expect_success \
      'git branch abc should create a branch' \
@@@ -72,17 -74,17 +75,17 @@@ test_expect_success 
          git branch -m n/n n
          test -f .git/logs/refs/heads/n'
  
 -test_expect_failure \
 -    'git branch -m o/o o should fail when o/p exists' \
 -       'git branch o/o &&
 +test_expect_success 'git branch -m o/o o should fail when o/p exists' '
 +      git branch o/o &&
          git branch o/p &&
 -        git branch -m o/o o'
 +      ! git branch -m o/o o
 +'
  
 -test_expect_failure \
 -    'git branch -m q r/q should fail when r exists' \
 -       'git branch q &&
 -         git branch r &&
 -         git branch -m q r/q'
 +test_expect_success 'git branch -m q r/q should fail when r exists' '
 +      git branch q &&
 +      git branch r &&
 +      ! git branch -m q r/q
 +'
  
  mv .git/config .git/config-saved
  
@@@ -109,13 -111,12 +112,13 @@@ test_expect_success 'config informatio
        "test $(git config branch.s.dummy) = Hello &&
         ! git config branch.s/s/dummy"
  
 -test_expect_failure \
 -    'git branch -m u v should fail when the reflog for u is a symlink' \
 -    'git branch -l u &&
 +test_expect_success \
 +    'git branch -m u v should fail when the reflog for u is a symlink' '
 +     git branch -l u &&
       mv .git/logs/refs/heads/u real-u &&
       ln -s real-u .git/logs/refs/heads/u &&
 -     git branch -m u v'
 +     ! git branch -m u v
 +'
  
  test_expect_success 'test tracking setup via --track' \
      'git config remote.local.url . &&
@@@ -171,7 -172,9 +174,9 @@@ test_expect_success 'test overriding tr
       ! test "$(git config branch.my2.merge)" = refs/heads/master'
  
  test_expect_success 'no tracking without .fetch entries' \
-     'git branch --track my6 s &&
+     'git config branch.autosetupmerge true &&
+      git branch my6 s &&
+      git config branch.automsetupmerge false &&
       test -z "$(git config branch.my6.remote)" &&
       test -z "$(git config branch.my6.merge)"'
  
@@@ -192,6 -195,21 +197,21 @@@ test_expect_success 'test deleting bran
      'git branch my7 s &&
       test "$(git branch -d my7 2>&1)" = "Deleted branch my7."'
  
+ test_expect_success 'test --track without .fetch entries' \
+     'git branch --track my8 &&
+      test "$(git config branch.my8.remote)" &&
+      test "$(git config branch.my8.merge)"'
+ test_expect_success \
+     'branch from non-branch HEAD w/autosetupmerge=always' \
+     'git config branch.autosetupmerge always &&
+      git branch my9 HEAD^ &&
+      git config branch.autosetupmerge false'
+ test_expect_success \
+     'branch from non-branch HEAD w/--track causes failure' \
+     '!(git branch --track my10 HEAD^)'
  # Keep this test last, as it changes the current branch
  cat >expect <<EOF
  0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000    branch: Created from master
diff --combined t/t7201-co.sh
@@@ -207,22 -207,6 +207,22 @@@ test_expect_success 'checkout to detac
        fi
  '
  
 +test_expect_success 'checkout to detach HEAD with :/message' '
 +
 +      git checkout -f master && git clean -f &&
 +      git checkout ":/Initial" &&
 +      H=$(git rev-parse --verify HEAD) &&
 +      M=$(git show-ref -s --verify refs/heads/master) &&
 +      test "z$H" = "z$M" &&
 +      if git symbolic-ref HEAD >/dev/null 2>&1
 +      then
 +              echo "OOPS, HEAD is still symbolic???"
 +              false
 +      else
 +              : happy
 +      fi
 +'
 +
  test_expect_success 'checkout to detach HEAD with HEAD^0' '
  
        git checkout -f master && git clean -f &&
@@@ -279,38 -263,28 +279,62 @@@ test_expect_success 'checkout with ambi
  
  '
  
 +test_expect_success 'switch branches while in subdirectory' '
 +
 +      git reset --hard &&
 +      git checkout master &&
 +
 +      mkdir subs &&
 +      (
 +              cd subs &&
 +              git checkout side
 +      ) &&
 +      ! test -f subs/one &&
 +      rm -fr subs
 +
 +'
 +
 +test_expect_success 'checkout specific path while in subdirectory' '
 +
 +      git reset --hard &&
 +      git checkout side &&
 +      mkdir subs &&
 +      >subs/bero &&
 +      git add subs/bero &&
 +      git commit -m "add subs/bero" &&
 +
 +      git checkout master &&
 +      mkdir -p subs &&
 +      (
 +              cd subs &&
 +              git checkout side -- bero
 +      ) &&
 +      test -f subs/bero
 +
 +'
 +
+ test_expect_success \
+     'checkout w/--track sets up tracking' '
+     git config branch.autosetupmerge false &&
+     git checkout master &&
+     git checkout --track -b track1 &&
+     test "$(git config branch.track1.remote)" &&
+     test "$(git config branch.track1.merge)"'
+ test_expect_success \
+     'checkout w/autosetupmerge=always sets up tracking' '
+     git config branch.autosetupmerge always &&
+     git checkout master &&
+     git checkout -b track2 &&
+     test "$(git config branch.track2.remote)" &&
+     test "$(git config branch.track2.merge)"
+     git config branch.autosetupmerge false'
+ test_expect_success \
+     'checkout w/--track from non-branch HEAD fails' '
+     git checkout -b delete-me master &&
+     rm .git/refs/heads/delete-me &&
+     test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
+     !(git checkout --track -b track)'
  test_done