diff options
-rw-r--r-- | Documentation/git-checkout.txt | 35 | ||||
-rw-r--r-- | builtin/checkout.c | 31 | ||||
-rwxr-xr-x | contrib/completion/git-completion.bash | 2 | ||||
-rw-r--r-- | refs.c | 55 | ||||
-rw-r--r-- | refs.h | 3 | ||||
-rwxr-xr-x | t/t2017-checkout-orphan.sh | 78 | ||||
-rwxr-xr-x | t/t3200-branch.sh | 24 |
7 files changed, 168 insertions, 60 deletions
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index afda5c36b..1548312b3 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -91,22 +91,29 @@ explicitly give a name with '-b' in such a case. details. --orphan:: - Create a new branch named <new_branch>, unparented to any other - branch. The new branch you switch to does not have any commit - and after the first one it will become the root of a new history - completely unconnected from all the other branches. + Create a new 'orphan' branch, named <new_branch>, started from + <start_point> and switch to it. The first commit made on this + new branch will have no parents and it will be the root of a new + history totally disconnected from all the other branches and + commits. + -When you use "--orphan", the index and the working tree are kept intact. -This allows you to start a new history that records set of paths similar -to that of the start-point commit, which is useful when you want to keep -different branches for different audiences you are working to like when -you have an open source and commercial versions of a software, for example. +The index and the working tree are adjusted as if you had previously run +"git checkout <start_point>". This allows you to start a new history +that records a set of paths similar to <start_point> by easily running +"git commit -a" to make the root commit. + -If you want to start a disconnected history that records set of paths -totally different from the original branch, you may want to first clear -the index and the working tree, by running "git rm -rf ." from the -top-level of the working tree, before preparing your files (by copying -from elsewhere, extracting a tarball, etc.) in the working tree. +This can be useful when you want to publish the tree from a commit +without exposing its full history. You might want to do this to publish +an open source branch of a project whose current tree is "clean", but +whose full history contains proprietary or otherwise encumbered bits of +code. ++ +If you want to start a disconnected history that records a set of paths +that is totally different from the one of <start_point>, then you should +clear the index and the working tree right after creating the orphan +branch by running "git rm -rf ." from the top level of the working tree. +Afterwards you will be ready to prepare your new files, repopulating the +working tree, by copying them from elsewhere, extracting a tarball, etc. -m:: --merge:: diff --git a/builtin/checkout.c b/builtin/checkout.c index c3825219c..72e4fbc72 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -493,7 +493,24 @@ static void update_refs_for_switch(struct checkout_opts *opts, struct strbuf msg = STRBUF_INIT; const char *old_desc; if (opts->new_branch) { - if (!opts->new_orphan_branch) + 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, 0, opts->new_branch_log, opts->track); new->name = opts->new_branch; @@ -517,6 +534,14 @@ static void update_refs_for_switch(struct checkout_opts *opts, opts->new_branch ? " a new" : "", 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); @@ -684,8 +709,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (opts.new_orphan_branch) { if (opts.new_branch) die("--orphan and -b are mutually exclusive"); - if (opts.track > 0 || opts.new_branch_log) - die("--orphan cannot be used with -t or -l"); + if (opts.track > 0) + die("--orphan cannot be used with -t"); opts.new_branch = opts.new_orphan_branch; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 57245a8c0..aebb0b689 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -842,7 +842,7 @@ _git_checkout () --*) __gitcomp " --quiet --ours --theirs --track --no-track --merge - --conflict= --patch + --conflict= --orphan --patch " ;; *) @@ -1258,52 +1258,65 @@ static int copy_msg(char *buf, const char *msg) return cp - buf; } -static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg) +int log_ref_setup(const char *ref_name, char *logfile, int bufsize) { - int logfd, written, oflags = O_APPEND | O_WRONLY; - unsigned maxlen, len; - int msglen; - char log_file[PATH_MAX]; - char *logrec; - const char *committer; - - if (log_all_ref_updates < 0) - log_all_ref_updates = !is_bare_repository(); - - git_snpath(log_file, sizeof(log_file), "logs/%s", ref_name); + int logfd, oflags = O_APPEND | O_WRONLY; + git_snpath(logfile, bufsize, "logs/%s", ref_name); if (log_all_ref_updates && (!prefixcmp(ref_name, "refs/heads/") || !prefixcmp(ref_name, "refs/remotes/") || !prefixcmp(ref_name, "refs/notes/") || !strcmp(ref_name, "HEAD"))) { - if (safe_create_leading_directories(log_file) < 0) + if (safe_create_leading_directories(logfile) < 0) return error("unable to create directory for %s", - log_file); + logfile); oflags |= O_CREAT; } - logfd = open(log_file, oflags, 0666); + logfd = open(logfile, oflags, 0666); if (logfd < 0) { if (!(oflags & O_CREAT) && errno == ENOENT) return 0; if ((oflags & O_CREAT) && errno == EISDIR) { - if (remove_empty_directories(log_file)) { + if (remove_empty_directories(logfile)) { return error("There are still logs under '%s'", - log_file); + logfile); } - logfd = open(log_file, oflags, 0666); + logfd = open(logfile, oflags, 0666); } if (logfd < 0) return error("Unable to append to %s: %s", - log_file, strerror(errno)); + logfile, strerror(errno)); } - adjust_shared_perm(log_file); + adjust_shared_perm(logfile); + close(logfd); + return 0; +} +static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg) +{ + int logfd, result, written, oflags = O_APPEND | O_WRONLY; + unsigned maxlen, len; + int msglen; + char log_file[PATH_MAX]; + char *logrec; + const char *committer; + + if (log_all_ref_updates < 0) + log_all_ref_updates = !is_bare_repository(); + + result = log_ref_setup(ref_name, log_file, sizeof(log_file)); + if (result) + return result; + + logfd = open(log_file, oflags); + if (logfd < 0) + return 0; msglen = msg ? strlen(msg) : 0; committer = git_committer_info(0); maxlen = strlen(committer) + msglen + 100; @@ -68,6 +68,9 @@ extern void unlock_ref(struct ref_lock *lock); /** Writes sha1 into the ref specified by the lock. **/ extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg); +/** Setup reflog before using. **/ +int log_ref_setup(const char *ref_name, char *logfile, int bufsize); + /** Reads log for the value of ref during at_time. **/ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt); diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh index a8297c61b..be88d4b5e 100755 --- a/t/t2017-checkout-orphan.sh +++ b/t/t2017-checkout-orphan.sh @@ -49,6 +49,62 @@ test_expect_success '--orphan must be rejected with -b' ' test refs/heads/master = "$(git symbolic-ref HEAD)" ' +test_expect_success '--orphan must be rejected with -t' ' + git checkout master && + test_must_fail git checkout --orphan new -t master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + +test_expect_success '--orphan ignores branch.autosetupmerge' ' + git checkout master && + git config branch.autosetupmerge always && + git checkout --orphan gamma && + test -z "$(git config branch.gamma.merge)" && + test refs/heads/gamma = "$(git symbolic-ref HEAD)" && + test_must_fail git rev-parse --verify HEAD^ +' + +test_expect_success '--orphan makes reflog by default' ' + git checkout master && + git config --unset core.logAllRefUpdates && + git checkout --orphan delta && + ! test -f .git/logs/refs/heads/delta && + test_must_fail PAGER= git reflog show delta && + git commit -m Delta && + test -f .git/logs/refs/heads/delta && + PAGER= git reflog show delta +' + +test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' ' + git checkout master && + git config core.logAllRefUpdates false && + git checkout --orphan epsilon && + ! test -f .git/logs/refs/heads/epsilon && + test_must_fail PAGER= git reflog show epsilon && + git commit -m Epsilon && + ! test -f .git/logs/refs/heads/epsilon && + test_must_fail PAGER= git reflog show epsilon +' + +test_expect_success '--orphan with -l makes reflog when core.logAllRefUpdates = false' ' + git checkout master && + git checkout -l --orphan zeta && + test -f .git/logs/refs/heads/zeta && + test_must_fail PAGER= git reflog show zeta && + git commit -m Zeta && + PAGER= git reflog show zeta +' + +test_expect_success 'giving up --orphan not committed when -l and core.logAllRefUpdates = false deletes reflog' ' + git checkout master && + git checkout -l --orphan eta && + test -f .git/logs/refs/heads/eta && + test_must_fail PAGER= git reflog show eta && + git checkout master && + ! test -f .git/logs/refs/heads/eta && + test_must_fail PAGER= git reflog show eta +' + test_expect_success '--orphan is rejected with an existing name' ' git checkout master && test_must_fail git checkout --orphan master && @@ -60,31 +116,11 @@ test_expect_success '--orphan refuses to switch if a merge is needed' ' git reset --hard && echo local >>"$TEST_FILE" && cat "$TEST_FILE" >"$TEST_FILE.saved" && - test_must_fail git checkout --orphan gamma master^ && + test_must_fail git checkout --orphan new master^ && test refs/heads/master = "$(git symbolic-ref HEAD)" && test_cmp "$TEST_FILE" "$TEST_FILE.saved" && git diff-index --quiet --cached HEAD && git reset --hard ' -test_expect_success '--orphan does not mix well with -t' ' - git checkout master && - test_must_fail git checkout -t master --orphan gamma && - test refs/heads/master = "$(git symbolic-ref HEAD)" -' - -test_expect_success '--orphan ignores branch.autosetupmerge' ' - git checkout -f master && - git config branch.autosetupmerge always && - git checkout --orphan delta && - test -z "$(git config branch.delta.merge)" && - test refs/heads/delta = "$(git symbolic-ref HEAD)" && - test_must_fail git rev-parse --verify HEAD^ -' - -test_expect_success '--orphan does not mix well with -l' ' - git checkout -f master && - test_must_fail git checkout -l --orphan gamma -' - test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index e0b760513..9d2c06ea6 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -224,6 +224,30 @@ test_expect_success \ test -f .git/logs/refs/heads/g/h/i && diff expect .git/logs/refs/heads/g/h/i' +test_expect_success 'checkout -b makes reflog by default' ' + git checkout master && + git config --unset core.logAllRefUpdates && + git checkout -b alpha && + test -f .git/logs/refs/heads/alpha && + PAGER= git reflog show alpha +' + +test_expect_success 'checkout -b does not make reflog when core.logAllRefUpdates = false' ' + git checkout master && + git config core.logAllRefUpdates false && + git checkout -b beta && + ! test -f .git/logs/refs/heads/beta && + test_must_fail PAGER= git reflog show beta +' + +test_expect_success 'checkout -b with -l makes reflog when core.logAllRefUpdates = false' ' + git checkout master && + git checkout -lb gamma && + git config --unset core.logAllRefUpdates && + test -f .git/logs/refs/heads/gamma && + PAGER= git reflog show gamma +' + test_expect_success 'avoid ambiguous track' ' git config branch.autosetupmerge true && git config remote.ambi1.url lalala && |