Merge branch 'po/userdiff-csharp'
[gitweb.git] / builtin / checkout.c
index 88b1f43e05e64f0e8dcd6dd0461bbebd6fab1e25..7f81120c728dce6fe712280f40583925eda264e3 100644 (file)
@@ -32,7 +32,12 @@ struct checkout_opts {
        int writeout_stage;
        int writeout_error;
 
+       /* not set by parse_options */
+       int branch_exists;
+
        const char *new_branch;
+       const char *new_branch_force;
+       const char *new_orphan_branch;
        int new_branch_log;
        enum branch_track track;
 };
@@ -278,7 +283,6 @@ static void show_local_changes(struct object *head)
        struct rev_info rev;
        /* I think we want full paths, even if we're in a subdirectory. */
        init_revisions(&rev, NULL);
-       rev.abbrev = 0;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
        if (diff_setup_done(&rev.diffopt) < 0)
                die("diff_setup_done failed");
@@ -492,8 +496,27 @@ static void update_refs_for_switch(struct checkout_opts *opts,
        struct strbuf msg = STRBUF_INIT;
        const char *old_desc;
        if (opts->new_branch) {
-               create_branch(old->name, opts->new_branch, new->name, 0,
-                             opts->new_branch_log, opts->track);
+               if (opts->new_orphan_branch) {
+                       if (opts->new_branch_log && !log_all_ref_updates) {
+                               int temp;
+                               char log_file[PATH_MAX];
+                               char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch);
+
+                               temp = log_all_ref_updates;
+                               log_all_ref_updates = 1;
+                               if (log_ref_setup(ref_name, log_file, sizeof(log_file))) {
+                                       fprintf(stderr, "Can not do reflog for '%s'\n",
+                                           opts->new_orphan_branch);
+                                       log_all_ref_updates = temp;
+                                       return;
+                               }
+                               log_all_ref_updates = temp;
+                       }
+               }
+               else
+                       create_branch(old->name, opts->new_branch, new->name,
+                                     opts->new_branch_force ? 1 : 0,
+                                     opts->new_branch_log, opts->track);
                new->name = opts->new_branch;
                setup_branch_path(new);
        }
@@ -510,11 +533,22 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                        if (old->path && !strcmp(new->path, old->path))
                                fprintf(stderr, "Already on '%s'\n",
                                        new->name);
-                       else
+                       else if (opts->new_branch)
                                fprintf(stderr, "Switched to%s branch '%s'\n",
-                                       opts->new_branch ? " a new" : "",
+                                       opts->branch_exists ? " and reset" : " a new",
+                                       new->name);
+                       else
+                               fprintf(stderr, "Switched to branch '%s'\n",
                                        new->name);
                }
+               if (old->path && old->name) {
+                       char log_file[PATH_MAX], ref_file[PATH_MAX];
+
+                       git_snpath(log_file, sizeof(log_file), "logs/%s", old->path);
+                       git_snpath(ref_file, sizeof(ref_file), "%s", old->path);
+                       if (!file_exists(ref_file) && file_exists(log_file))
+                               remove_path(log_file);
+               }
        } else if (strcmp(new->name, "HEAD")) {
                update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
                           REF_NODEREF, DIE_ON_ERR);
@@ -609,7 +643,8 @@ static int check_tracking_name(const char *refname, const unsigned char *sha1,
 
 static const char *unique_tracking_name(const char *name)
 {
-       struct tracking_name_data cb_data = { name, NULL, 1 };
+       struct tracking_name_data cb_data = { NULL, NULL, 1 };
+       cb_data.name = name;
        for_each_ref(check_tracking_name, &cb_data);
        if (cb_data.unique)
                return cb_data.remote;
@@ -629,10 +664,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        int dwim_new_local_branch = 1;
        struct option options[] = {
                OPT__QUIET(&opts.quiet),
-               OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
+               OPT_STRING('b', NULL, &opts.new_branch, "branch",
+                          "create and checkout a new branch"),
+               OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
+                          "create/reset and checkout a branch"),
                OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
                OPT_SET_INT('t', "track",  &opts.track, "track",
                        BRANCH_TRACK_EXPLICIT),
+               OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
                OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
                            2),
                OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
@@ -659,6 +698,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
 
+       /* we can assume from now on new_branch = !new_branch_force */
+       if (opts.new_branch && opts.new_branch_force)
+               die("-B cannot be used with -b");
+
+       /* copy -B over to -b, so that we can just check the latter */
+       if (opts.new_branch_force)
+               opts.new_branch = opts.new_branch_force;
+
        if (patch_mode && (opts.track > 0 || opts.new_branch
                           || opts.new_branch_log || opts.merge || opts.force))
                die ("--patch is incompatible with all other options");
@@ -678,6 +725,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                opts.new_branch = argv0 + 1;
        }
 
+       if (opts.new_orphan_branch) {
+               if (opts.new_branch)
+                       die("--orphan and -b|-B are mutually exclusive");
+               if (opts.track > 0)
+                       die("--orphan cannot be used with -t");
+               opts.new_branch = opts.new_orphan_branch;
+       }
+
        if (conflict_style) {
                opts.merge = 1; /* implied */
                git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
@@ -821,8 +876,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                if (strbuf_check_branch_ref(&buf, opts.new_branch))
                        die("git checkout: we do not like '%s' as a branch name.",
                            opts.new_branch);
-               if (!get_sha1(buf.buf, rev))
-                       die("git checkout: branch %s already exists", opts.new_branch);
+               if (!get_sha1(buf.buf, rev)) {
+                       opts.branch_exists = 1;
+                       if (!opts.new_branch_force)
+                               die("git checkout: branch %s already exists",
+                                   opts.new_branch);
+               }
                strbuf_release(&buf);
        }