"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
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.
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.
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.
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.
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.
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
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) */
}
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 "";
}
{
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);
char *new_branch;
int new_branch_log;
- int track;
+ enum branch_track track;
};
struct branch_info {
}
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);
}
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);
}
};
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)
{
}
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)
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);
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];
/* 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);
/* 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);
/* 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 */
#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 *);
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 */
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;
}
{
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;
}
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;
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);
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;
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);
+}
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;
'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' \
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
"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 . &&
! 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)"'
'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
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 &&
'
+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