Merge branch 'jc/strbuf-getline'
authorJunio C Hamano <gitster@pobox.com>
Fri, 29 Jan 2016 00:10:14 +0000 (16:10 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 29 Jan 2016 00:10:14 +0000 (16:10 -0800)
The preliminary clean-up for jc/peace-with-crlf topic.

* jc/strbuf-getline:
strbuf: give strbuf_getline() to the "most text friendly" variant
checkout-index: there are only two possible line terminations
update-index: there are only two possible line terminations
check-ignore: there are only two possible line terminations
check-attr: there are only two possible line terminations
mktree: there are only two possible line terminations
strbuf: introduce strbuf_getline_{lf,nul}()
strbuf: make strbuf_getline_crlf() global
strbuf: miniscule style fix

1  2 
builtin/am.c
builtin/clone.c
builtin/commit.c
builtin/grep.c
builtin/notes.c
builtin/pull.c
strbuf.c
strbuf.h
diff --combined builtin/am.c
index de235cf11a9df925f482d02ec0df7c6e748ff35d,7b8351dede1a70e0d6eefdfd9ce30d37c86ca640..b55f903f058047bc59773dd691892c621351f9ba
@@@ -45,21 -45,6 +45,6 @@@ static int is_empty_file(const char *fi
        return !st.st_size;
  }
  
- /**
-  * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
-  */
- static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
- {
-       if (strbuf_getwholeline(sb, fp, '\n'))
-               return EOF;
-       if (sb->buf[sb->len - 1] == '\n') {
-               strbuf_setlen(sb, sb->len - 1);
-               if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
-                       strbuf_setlen(sb, sb->len - 1);
-       }
-       return 0;
- }
  /**
   * Returns the length of the first line of msg.
   */
@@@ -284,7 -269,7 +269,7 @@@ static char *read_shell_var(FILE *fp, c
        struct strbuf sb = STRBUF_INIT;
        const char *str;
  
-       if (strbuf_getline(&sb, fp, '\n'))
+       if (strbuf_getline_lf(&sb, fp))
                goto fail;
  
        if (!skip_prefix(sb.buf, key, &str))
@@@ -573,7 -558,7 +558,7 @@@ static int copy_notes_for_rebase(const 
  
        fp = xfopen(am_path(state, "rewritten"), "r");
  
-       while (!strbuf_getline(&sb, fp, '\n')) {
+       while (!strbuf_getline_lf(&sb, fp)) {
                unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ];
  
                if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
@@@ -628,7 -613,7 +613,7 @@@ static int is_mail(FILE *fp
        if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
                die("invalid pattern: %s", header_regex);
  
-       while (!strbuf_getline_crlf(&sb, fp)) {
+       while (!strbuf_getline(&sb, fp)) {
                if (!sb.len)
                        break; /* End of header */
  
@@@ -675,7 -660,7 +660,7 @@@ static int detect_patch_format(const ch
  
        fp = xfopen(*paths, "r");
  
-       while (!strbuf_getline_crlf(&l1, fp)) {
+       while (!strbuf_getline(&l1, fp)) {
                if (l1.len)
                        break;
        }
        }
  
        strbuf_reset(&l2);
-       strbuf_getline_crlf(&l2, fp);
+       strbuf_getline(&l2, fp);
        strbuf_reset(&l3);
-       strbuf_getline_crlf(&l3, fp);
+       strbuf_getline(&l3, fp);
  
        /*
         * If the second line is empty and the third is a From, Author or Date
@@@ -817,7 -802,7 +802,7 @@@ static int stgit_patch_to_mail(FILE *ou
        struct strbuf sb = STRBUF_INIT;
        int subject_printed = 0;
  
-       while (!strbuf_getline(&sb, in, '\n')) {
+       while (!strbuf_getline_lf(&sb, in)) {
                const char *str;
  
                if (str_isspace(sb.buf))
@@@ -875,7 -860,7 +860,7 @@@ static int split_mail_stgit_series(stru
                return error(_("could not open '%s' for reading: %s"), *paths,
                                strerror(errno));
  
-       while (!strbuf_getline(&sb, fp, '\n')) {
+       while (!strbuf_getline_lf(&sb, fp)) {
                if (*sb.buf == '#')
                        continue; /* skip comment lines */
  
@@@ -900,7 -885,7 +885,7 @@@ static int hg_patch_to_mail(FILE *out, 
  {
        struct strbuf sb = STRBUF_INIT;
  
-       while (!strbuf_getline(&sb, in, '\n')) {
+       while (!strbuf_getline_lf(&sb, in)) {
                const char *str;
  
                if (skip_prefix(sb.buf, "# User ", &str))
@@@ -1317,7 -1302,7 +1302,7 @@@ static int parse_mail(struct am_state *
  
        /* Extract message and author information */
        fp = xfopen(am_path(state, "info"), "r");
-       while (!strbuf_getline(&sb, fp, '\n')) {
+       while (!strbuf_getline_lf(&sb, fp)) {
                const char *x;
  
                if (skip_prefix(sb.buf, "Subject: ", &x)) {
@@@ -1383,7 -1368,7 +1368,7 @@@ static int get_mail_commit_sha1(unsigne
        FILE *fp = xfopen(mail, "r");
        const char *x;
  
-       if (strbuf_getline(&sb, fp, '\n'))
+       if (strbuf_getline_lf(&sb, fp))
                return -1;
  
        if (!skip_prefix(sb.buf, "From ", &x))
@@@ -1939,7 -1924,6 +1924,7 @@@ next
         */
        if (!state->rebasing) {
                am_destroy(state);
 +              close_all_packs();
                run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
        }
  }
diff --combined builtin/clone.c
index a7c8def8cb7199e82acad9e94bb01d93ba4ef0fb,29741f44460a6648557ffe4619e71ca64cef3e63..81e238f738bae80b19524fee83f9046be3571cc2
@@@ -339,7 -339,7 +339,7 @@@ static void copy_alternates(struct strb
        FILE *in = fopen(src->buf, "r");
        struct strbuf line = STRBUF_INIT;
  
-       while (strbuf_getline(&line, in, '\n') != EOF) {
+       while (strbuf_getline_lf(&line, in) != EOF) {
                char *abs_path;
                if (!line.len || line.buf[0] == '#')
                        continue;
@@@ -636,11 -636,9 +636,11 @@@ static void update_remote_refs(const st
                struct strbuf head_ref = STRBUF_INIT;
                strbuf_addstr(&head_ref, branch_top);
                strbuf_addstr(&head_ref, "HEAD");
 -              create_symref(head_ref.buf,
 -                            remote_head_points_at->peer_ref->name,
 -                            msg);
 +              if (create_symref(head_ref.buf,
 +                                remote_head_points_at->peer_ref->name,
 +                                msg) < 0)
 +                      die("unable to update %s", head_ref.buf);
 +              strbuf_release(&head_ref);
        }
  }
  
@@@ -650,8 -648,7 +650,8 @@@ static void update_head(const struct re
        const char *head;
        if (our && skip_prefix(our->name, "refs/heads/", &head)) {
                /* Local default branch link */
 -              create_symref("HEAD", our->name, NULL);
 +              if (create_symref("HEAD", our->name, NULL) < 0)
 +                      die("unable to update HEAD");
                if (!option_bare) {
                        update_ref(msg, "HEAD", our->old_oid.hash, NULL, 0,
                                   UPDATE_REFS_DIE_ON_ERR);
diff --combined builtin/commit.c
index 89bf6ad38abbbee068ca29bad2ce2a4b728fa388,d9db59e1d275fbe0fac5c3ca461d8fdec3adcf2e..b3bd2d41813f80d848afc199c2fae92cae6b6c11
@@@ -761,7 -761,7 +761,7 @@@ static int prepare_to_commit(const cha
                hook_arg2 = "";
        }
  
 -      s->fp = fopen(git_path(commit_editmsg), "w");
 +      s->fp = fopen_for_writing(git_path(commit_editmsg));
        if (s->fp == NULL)
                die_errno(_("could not open '%s'"), git_path(commit_editmsg));
  
@@@ -1690,7 -1690,7 +1690,7 @@@ int cmd_commit(int argc, const char **a
                if (fp == NULL)
                        die_errno(_("could not open '%s' for reading"),
                                  git_path_merge_head());
-               while (strbuf_getline(&m, fp, '\n') != EOF) {
+               while (strbuf_getline_lf(&m, fp) != EOF) {
                        struct commit *parent;
  
                        parent = get_merge_parent(m.buf);
diff --combined builtin/grep.c
index 5526fd705657ff80db630ec4f29554893b6c09ad,5a5beb810954afcf98d27a9615f2380f7e8b06c4..6c030dad6b7782478a07a4feb6020e4a30e7f7a0
@@@ -24,11 -24,11 +24,11 @@@ static char const * const grep_usage[] 
        NULL
  };
  
 -static int use_threads = 1;
 +#define GREP_NUM_THREADS_DEFAULT 8
 +static int num_threads;
  
  #ifndef NO_PTHREADS
 -#define THREADS 8
 -static pthread_t threads[THREADS];
 +static pthread_t *threads;
  
  /* We use one producer thread and THREADS consumer
   * threads. The producer adds struct work_items to 'todo' and the
@@@ -63,13 -63,13 +63,13 @@@ static pthread_mutex_t grep_mutex
  
  static inline void grep_lock(void)
  {
 -      if (use_threads)
 +      if (num_threads)
                pthread_mutex_lock(&grep_mutex);
  }
  
  static inline void grep_unlock(void)
  {
 -      if (use_threads)
 +      if (num_threads)
                pthread_mutex_unlock(&grep_mutex);
  }
  
@@@ -206,8 -206,7 +206,8 @@@ static void start_threads(struct grep_o
                strbuf_init(&todo[i].out, 0);
        }
  
 -      for (i = 0; i < ARRAY_SIZE(threads); i++) {
 +      threads = xcalloc(num_threads, sizeof(*threads));
 +      for (i = 0; i < num_threads; i++) {
                int err;
                struct grep_opt *o = grep_opt_dup(opt);
                o->output = strbuf_out;
@@@ -239,14 -238,12 +239,14 @@@ static int wait_all(void
        pthread_cond_broadcast(&cond_add);
        grep_unlock();
  
 -      for (i = 0; i < ARRAY_SIZE(threads); i++) {
 +      for (i = 0; i < num_threads; i++) {
                void *h;
                pthread_join(threads[i], &h);
                hit |= (int) (intptr_t) h;
        }
  
 +      free(threads);
 +
        pthread_mutex_destroy(&grep_mutex);
        pthread_mutex_destroy(&grep_read_mutex);
        pthread_mutex_destroy(&grep_attr_mutex);
@@@ -270,14 -267,6 +270,14 @@@ static int grep_cmd_config(const char *
        int st = grep_config(var, value, cb);
        if (git_color_default_config(var, value, cb) < 0)
                st = -1;
 +
 +      if (!strcmp(var, "grep.threads")) {
 +              num_threads = git_config_int(var, value);
 +              if (num_threads < 0)
 +                      die(_("invalid number of threads specified (%d) for %s"),
 +                          num_threads, var);
 +      }
 +
        return st;
  }
  
@@@ -305,7 -294,7 +305,7 @@@ static int grep_sha1(struct grep_opt *o
        }
  
  #ifndef NO_PTHREADS
 -      if (use_threads) {
 +      if (num_threads) {
                add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
                strbuf_release(&pathbuf);
                return 0;
@@@ -334,7 -323,7 +334,7 @@@ static int grep_file(struct grep_opt *o
                strbuf_addstr(&buf, filename);
  
  #ifndef NO_PTHREADS
 -      if (use_threads) {
 +      if (num_threads) {
                add_work(opt, GREP_SOURCE_FILE, buf.buf, filename, filename);
                strbuf_release(&buf);
                return 0;
@@@ -386,7 -375,7 +386,7 @@@ static int grep_cache(struct grep_opt *
  
        for (nr = 0; nr < active_nr; nr++) {
                const struct cache_entry *ce = active_cache[nr];
 -              if (!S_ISREG(ce->ce_mode))
 +              if (!S_ISREG(ce->ce_mode) || ce_intent_to_add(ce))
                        continue;
                if (!ce_path_match(ce, pathspec, NULL))
                        continue;
@@@ -573,7 -562,7 +573,7 @@@ static int file_callback(const struct o
        patterns = from_stdin ? stdin : fopen(arg, "r");
        if (!patterns)
                die_errno(_("cannot open '%s'"), arg);
-       while (strbuf_getline(&sb, patterns, '\n') == 0) {
+       while (strbuf_getline_lf(&sb, patterns) == 0) {
                /* ignore empty line like grep does */
                if (sb.len == 0)
                        continue;
@@@ -708,8 -697,6 +708,8 @@@ int cmd_grep(int argc, const char **arg
                        N_("show <n> context lines before matches")),
                OPT_INTEGER('A', "after-context", &opt.post_context,
                        N_("show <n> context lines after matches")),
 +              OPT_INTEGER(0, "threads", &num_threads,
 +                      N_("use <n> worker threads")),
                OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"),
                        context_callback),
                OPT_BOOL('p', "show-function", &opt.funcname,
                             PARSE_OPT_STOP_AT_NON_OPTION);
        grep_commit_pattern_type(pattern_type_arg, &opt);
  
 -      if (use_index && !startup_info->have_repository)
 -              /* die the same way as if we did it at the beginning */
 -              setup_git_directory();
 +      if (use_index && !startup_info->have_repository) {
 +              int fallback = 0;
 +              git_config_get_bool("grep.fallbacktonoindex", &fallback);
 +              if (fallback)
 +                      use_index = 0;
 +              else
 +                      /* die the same way as if we did it at the beginning */
 +                      setup_git_directory();
 +      }
  
        /*
         * skip a -- separator; we know it cannot be
                opt.output_priv = &path_list;
                opt.output = append_path;
                string_list_append(&path_list, show_in_pager);
 -              use_threads = 0;
        }
  
        if (!opt.pattern_list)
        }
  
  #ifndef NO_PTHREADS
 -      if (list.nr || cached || online_cpus() == 1)
 -              use_threads = 0;
 +      if (list.nr || cached || show_in_pager)
 +              num_threads = 0;
 +      else if (num_threads == 0)
 +              num_threads = GREP_NUM_THREADS_DEFAULT;
 +      else if (num_threads < 0)
 +              die(_("invalid number of threads specified (%d)"), num_threads);
  #else
 -      use_threads = 0;
 +      num_threads = 0;
  #endif
  
  #ifndef NO_PTHREADS
 -      if (use_threads) {
 +      if (num_threads) {
                if (!(opt.name_only || opt.unmatch_name_only || opt.count)
                    && (opt.pre_context || opt.post_context ||
                        opt.file_break || opt.funcbody))
                hit = grep_objects(&opt, &pathspec, &list);
        }
  
 -      if (use_threads)
 +      if (num_threads)
                hit |= wait_all();
        if (hit && show_in_pager)
                run_pager(&opt, prefix);
diff --combined builtin/notes.c
index e1556896f9ea653f562e073f9ec23bfdcd0d4d7b,3775e389635a58f20538b621fd22980a38b78b0d..9d54934d6c4ccf55ed24304a3da5a6fd0f0206ca
@@@ -286,11 -286,11 +286,11 @@@ static int notes_copy_from_stdin(int fo
                if (!c)
                        return 0;
        } else {
 -              init_notes(NULL, NULL, NULL, 0);
 +              init_notes(NULL, NULL, NULL, NOTES_INIT_WRITABLE);
                t = &default_notes_tree;
        }
  
-       while (strbuf_getline(&buf, stdin, '\n') != EOF) {
+       while (strbuf_getline_lf(&buf, stdin) != EOF) {
                unsigned char from_obj[20], to_obj[20];
                struct strbuf **split;
                int err;
        return ret;
  }
  
 -static struct notes_tree *init_notes_check(const char *subcommand)
 +static struct notes_tree *init_notes_check(const char *subcommand,
 +                                         int flags)
  {
        struct notes_tree *t;
 -      init_notes(NULL, NULL, NULL, 0);
 +      const char *ref;
 +      init_notes(NULL, NULL, NULL, flags);
        t = &default_notes_tree;
  
 -      if (!starts_with(t->ref, "refs/notes/"))
 +      ref = (flags & NOTES_INIT_WRITABLE) ? t->update_ref : t->ref;
 +      if (!starts_with(ref, "refs/notes/"))
                die("Refusing to %s notes in %s (outside of refs/notes/)",
 -                  subcommand, t->ref);
 +                  subcommand, ref);
        return t;
  }
  
@@@ -363,7 -360,7 +363,7 @@@ static int list(int argc, const char **
                usage_with_options(git_notes_list_usage, options);
        }
  
 -      t = init_notes_check("list");
 +      t = init_notes_check("list", 0);
        if (argc) {
                if (get_sha1(argv[0], object))
                        die(_("Failed to resolve '%s' as a valid ref."), argv[0]);
@@@ -423,7 -420,7 +423,7 @@@ static int add(int argc, const char **a
        if (get_sha1(object_ref, object))
                die(_("Failed to resolve '%s' as a valid ref."), object_ref);
  
 -      t = init_notes_check("add");
 +      t = init_notes_check("add", NOTES_INIT_WRITABLE);
        note = get_note(t, object);
  
        if (note) {
@@@ -514,7 -511,7 +514,7 @@@ static int copy(int argc, const char **
        if (get_sha1(object_ref, object))
                die(_("Failed to resolve '%s' as a valid ref."), object_ref);
  
 -      t = init_notes_check("copy");
 +      t = init_notes_check("copy", NOTES_INIT_WRITABLE);
        note = get_note(t, object);
  
        if (note) {
@@@ -592,7 -589,7 +592,7 @@@ static int append_edit(int argc, const 
        if (get_sha1(object_ref, object))
                die(_("Failed to resolve '%s' as a valid ref."), object_ref);
  
 -      t = init_notes_check(argv[0]);
 +      t = init_notes_check(argv[0], NOTES_INIT_WRITABLE);
        note = get_note(t, object);
  
        prepare_note_data(object, &d, edit ? note : NULL);
@@@ -655,7 -652,7 +655,7 @@@ static int show(int argc, const char **
        if (get_sha1(object_ref, object))
                die(_("Failed to resolve '%s' as a valid ref."), object_ref);
  
 -      t = init_notes_check("show");
 +      t = init_notes_check("show", 0);
        note = get_note(t, object);
  
        if (!note)
@@@ -812,7 -809,7 +812,7 @@@ static int merge(int argc, const char *
        expand_notes_ref(&remote_ref);
        o.remote_ref = remote_ref.buf;
  
 -      t = init_notes_check("merge");
 +      t = init_notes_check("merge", NOTES_INIT_WRITABLE);
  
        if (strategy) {
                if (parse_notes_merge_strategy(strategy, &o.strategy)) {
@@@ -904,7 -901,7 +904,7 @@@ static int remove_cmd(int argc, const c
        argc = parse_options(argc, argv, prefix, options,
                             git_notes_remove_usage, 0);
  
 -      t = init_notes_check("remove");
 +      t = init_notes_check("remove", NOTES_INIT_WRITABLE);
  
        if (!argc && !from_stdin) {
                retval = remove_one_note(t, "HEAD", flag);
@@@ -946,7 -943,7 +946,7 @@@ static int prune(int argc, const char *
                usage_with_options(git_notes_prune_usage, options);
        }
  
 -      t = init_notes_check("prune");
 +      t = init_notes_check("prune", NOTES_INIT_WRITABLE);
  
        prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) |
                (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) );
diff --combined builtin/pull.c
index c713fe065beb2ac9d1c8058bf101094e99199766,52606a84cebf1421f5378233d1915df5233c8737..10eff03967e0b9cb473d262b443949c55681fd1b
@@@ -22,8 -22,7 +22,8 @@@ enum rebase_type 
        REBASE_INVALID = -1,
        REBASE_FALSE = 0,
        REBASE_TRUE,
 -      REBASE_PRESERVE
 +      REBASE_PRESERVE,
 +      REBASE_INTERACTIVE
  };
  
  /**
@@@ -43,8 -42,6 +43,8 @@@ static enum rebase_type parse_config_re
                return REBASE_TRUE;
        else if (!strcmp(value, "preserve"))
                return REBASE_PRESERVE;
 +      else if (!strcmp(value, "interactive"))
 +              return REBASE_INTERACTIVE;
  
        if (fatal)
                die(_("Invalid value for %s: %s"), key, value);
@@@ -98,7 -95,6 +98,7 @@@ static int opt_force
  static char *opt_tags;
  static char *opt_prune;
  static char *opt_recurse_submodules;
 +static char *max_children;
  static int opt_dry_run;
  static char *opt_keep;
  static char *opt_depth;
@@@ -116,7 -112,7 +116,7 @@@ static struct option pull_options[] = 
        /* Options passed to git-merge or git-rebase */
        OPT_GROUP(N_("Options related to merging")),
        { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
 -        "false|true|preserve",
 +        "false|true|preserve|interactive",
          N_("incorporate changes by rebasing rather than merging"),
          PARSE_OPT_OPTARG, parse_opt_rebase },
        OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
                N_("on-demand"),
                N_("control recursive fetching of submodules"),
                PARSE_OPT_OPTARG),
 +      OPT_PASSTHRU('j', "jobs", &max_children, N_("n"),
 +              N_("number of submodules pulled in parallel"),
 +              PARSE_OPT_OPTARG),
        OPT_BOOL(0, "dry-run", &opt_dry_run,
                N_("dry run")),
        OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
@@@ -385,7 -378,7 +385,7 @@@ static void get_merge_heads(struct sha1
  
        if (!(fp = fopen(filename, "r")))
                die_errno(_("could not open '%s' for reading"), filename);
-       while (strbuf_getline(&sb, fp, '\n') != EOF) {
+       while (strbuf_getline_lf(&sb, fp) != EOF) {
                if (get_sha1_hex(sb.buf, sha1))
                        continue;  /* invalid line: does not start with SHA1 */
                if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t"))
@@@ -532,8 -525,6 +532,8 @@@ static int run_fetch(const char *repo, 
                argv_array_push(&args, opt_prune);
        if (opt_recurse_submodules)
                argv_array_push(&args, opt_recurse_submodules);
 +      if (max_children)
 +              argv_array_push(&args, max_children);
        if (opt_dry_run)
                argv_array_push(&args, "--dry-run");
        if (opt_keep)
@@@ -781,8 -772,6 +781,8 @@@ static int run_rebase(const unsigned ch
        /* Options passed to git-rebase */
        if (opt_rebase == REBASE_PRESERVE)
                argv_array_push(&args, "--preserve-merges");
 +      else if (opt_rebase == REBASE_INTERACTIVE)
 +              argv_array_push(&args, "--interactive");
        if (opt_diffstat)
                argv_array_push(&args, opt_diffstat);
        argv_array_pushv(&args, opt_strategies.argv);
diff --combined strbuf.c
index 38686ffb65c2de953f9b224a5eecd22188081417,47ac0457f761152451994cf8d9e6dd14f086b283..bab316dda8f4946efb9a908f17c98b4db3d21e98
+++ b/strbuf.c
@@@ -384,17 -384,6 +384,17 @@@ ssize_t strbuf_read(struct strbuf *sb, 
        return sb->len - oldlen;
  }
  
 +ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint)
 +{
 +      ssize_t cnt;
 +
 +      strbuf_grow(sb, hint ? hint : 8192);
 +      cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
 +      if (cnt > 0)
 +              strbuf_setlen(sb, sb->len + cnt);
 +      return cnt;
 +}
 +
  #define STRBUF_MAXLINK (2*PATH_MAX)
  
  int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
@@@ -512,15 -501,37 +512,37 @@@ int strbuf_getwholeline(struct strbuf *
  }
  #endif
  
int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
static int strbuf_getdelim(struct strbuf *sb, FILE *fp, int term)
  {
        if (strbuf_getwholeline(sb, fp, term))
                return EOF;
-       if (sb->buf[sb->len-1] == term)
-               strbuf_setlen(sb, sb->len-1);
+       if (sb->buf[sb->len - 1] == term)
+               strbuf_setlen(sb, sb->len - 1);
        return 0;
  }
  
+ int strbuf_getline(struct strbuf *sb, FILE *fp)
+ {
+       if (strbuf_getwholeline(sb, fp, '\n'))
+               return EOF;
+       if (sb->buf[sb->len - 1] == '\n') {
+               strbuf_setlen(sb, sb->len - 1);
+               if (sb->len && sb->buf[sb->len - 1] == '\r')
+                       strbuf_setlen(sb, sb->len - 1);
+       }
+       return 0;
+ }
+ int strbuf_getline_lf(struct strbuf *sb, FILE *fp)
+ {
+       return strbuf_getdelim(sb, fp, '\n');
+ }
+ int strbuf_getline_nul(struct strbuf *sb, FILE *fp)
+ {
+       return strbuf_getdelim(sb, fp, '\0');
+ }
  int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term)
  {
        strbuf_reset(sb);
diff --combined strbuf.h
index 2bf90e70fcf1e9bcbfcbfdf4bc8b30e1d7b61d10,970c24ab43b3e7a81cee0e19a8a222d0d08dd877..f72fd14c2eaded0399b779150ea1565edd7bf47a
+++ b/strbuf.h
@@@ -354,8 -354,8 +354,8 @@@ extern void strbuf_addftime(struct strb
   *
   * NOTE: The buffer is rewound if the read fails. If -1 is returned,
   * `errno` must be consulted, like you would do for `read(3)`.
-  * `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline()` has the
-  * same behaviour as well.
+  * `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline_*()`
+  * family of functions have the same behaviour as well.
   */
  extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
  
   */
  extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
  
 +/**
 + * Read the contents of a given file descriptor partially by using only one
 + * attempt of xread. The third argument can be used to give a hint about the
 + * file size, to avoid reallocs. Returns the number of new bytes appended to
 + * the sb.
 + */
 +extern ssize_t strbuf_read_once(struct strbuf *, int fd, size_t hint);
 +
  /**
   * Read the contents of a file, specified by its path. The third argument
   * can be used to give a hint about the file size, to avoid reallocs.
@@@ -387,14 -379,31 +387,31 @@@ extern ssize_t strbuf_read_file(struct 
  extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);
  
  /**
-  * Read a line from a FILE *, overwriting the existing contents
-  * of the strbuf. The second argument specifies the line
-  * terminator character, typically `'\n'`.
+  * Read a line from a FILE *, overwriting the existing contents of
+  * the strbuf.  The strbuf_getline*() family of functions share
+  * this signature, but have different line termination conventions.
+  *
   * Reading stops after the terminator or at EOF.  The terminator
   * is removed from the buffer before returning.  Returns 0 unless
   * there was nothing left before EOF, in which case it returns `EOF`.
   */
- extern int strbuf_getline(struct strbuf *, FILE *, int);
+ typedef int (*strbuf_getline_fn)(struct strbuf *, FILE *);
+ /* Uses LF as the line terminator */
+ extern int strbuf_getline_lf(struct strbuf *sb, FILE *fp);
+ /* Uses NUL as the line terminator */
+ extern int strbuf_getline_nul(struct strbuf *sb, FILE *fp);
+ /*
+  * Similar to strbuf_getline_lf(), but additionally treats a CR that
+  * comes immediately before the LF as part of the terminator.
+  * This is the most friendly version to be used to read "text" files
+  * that can come from platforms whose native text format is CRLF
+  * terminated.
+  */
+ extern int strbuf_getline(struct strbuf *, FILE *);
  
  /**
   * Like `strbuf_getline`, but keeps the trailing terminator (if