Merge branch 'em/checkout-orphan'

* em/checkout-orphan:
  log_ref_setup: don't return stack-allocated array
  bash completion: add --orphan to 'git checkout'
  t3200: test -l with core.logAllRefUpdates options
  checkout --orphan: respect -l option always
  refs: split log_ref_write logic into log_ref_setup
  Documentation: alter checkout --orphan description
This commit is contained in:
Junio C Hamano 2010-06-21 06:02:41 -07:00
commit 5bebcd4ecb
7 changed files with 180 additions and 72 deletions

View File

@ -91,22 +91,29 @@ explicitly give a name with '-b' in such a case.
details. details.
--orphan:: --orphan::
Create a new branch named <new_branch>, unparented to any other Create a new 'orphan' branch, named <new_branch>, started from
branch. The new branch you switch to does not have any commit <start_point> and switch to it. The first commit made on this
and after the first one it will become the root of a new history new branch will have no parents and it will be the root of a new
completely unconnected from all the other branches. history totally disconnected from all the other branches and
commits.
+ +
When you use "--orphan", the index and the working tree are kept intact. The index and the working tree are adjusted as if you had previously run
This allows you to start a new history that records set of paths similar "git checkout <start_point>". This allows you to start a new history
to that of the start-point commit, which is useful when you want to keep that records a set of paths similar to <start_point> by easily running
different branches for different audiences you are working to like when "git commit -a" to make the root commit.
you have an open source and commercial versions of a software, for example.
+ +
If you want to start a disconnected history that records set of paths This can be useful when you want to publish the tree from a commit
totally different from the original branch, you may want to first clear without exposing its full history. You might want to do this to publish
the index and the working tree, by running "git rm -rf ." from the an open source branch of a project whose current tree is "clean", but
top-level of the working tree, before preparing your files (by copying whose full history contains proprietary or otherwise encumbered bits of
from elsewhere, extracting a tarball, etc.) in the working tree. 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:: -m::
--merge:: --merge::

View File

@ -493,7 +493,24 @@ static void update_refs_for_switch(struct checkout_opts *opts,
struct strbuf msg = STRBUF_INIT; struct strbuf msg = STRBUF_INIT;
const char *old_desc; const char *old_desc;
if (opts->new_branch) { 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, create_branch(old->name, opts->new_branch, new->name, 0,
opts->new_branch_log, opts->track); opts->new_branch_log, opts->track);
new->name = opts->new_branch; new->name = opts->new_branch;
@ -517,6 +534,14 @@ static void update_refs_for_switch(struct checkout_opts *opts,
opts->new_branch ? " a new" : "", opts->new_branch ? " a new" : "",
new->name); 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")) { } else if (strcmp(new->name, "HEAD")) {
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
REF_NODEREF, DIE_ON_ERR); 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_orphan_branch) {
if (opts.new_branch) if (opts.new_branch)
die("--orphan and -b are mutually exclusive"); die("--orphan and -b are mutually exclusive");
if (opts.track > 0 || opts.new_branch_log) if (opts.track > 0)
die("--orphan cannot be used with -t or -l"); die("--orphan cannot be used with -t");
opts.new_branch = opts.new_orphan_branch; opts.new_branch = opts.new_orphan_branch;
} }

View File

@ -842,7 +842,7 @@ _git_checkout ()
--*) --*)
__gitcomp " __gitcomp "
--quiet --ours --theirs --track --no-track --merge --quiet --ours --theirs --track --no-track --merge
--conflict= --patch --conflict= --orphan --patch
" "
;; ;;
*) *)

77
refs.c
View File

@ -1258,10 +1258,49 @@ static int copy_msg(char *buf, const char *msg)
return cp - buf; return cp - buf;
} }
int log_ref_setup(const char *ref_name, char *logfile, int bufsize)
{
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(logfile) < 0)
return error("unable to create directory for %s",
logfile);
oflags |= O_CREAT;
}
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(logfile)) {
return error("There are still logs under '%s'",
logfile);
}
logfd = open(logfile, oflags, 0666);
}
if (logfd < 0)
return error("Unable to append to %s: %s",
logfile, strerror(errno));
}
adjust_shared_perm(logfile);
close(logfd);
return 0;
}
static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
const unsigned char *new_sha1, const char *msg) const unsigned char *new_sha1, const char *msg)
{ {
int logfd, written, oflags = O_APPEND | O_WRONLY; int logfd, result, written, oflags = O_APPEND | O_WRONLY;
unsigned maxlen, len; unsigned maxlen, len;
int msglen; int msglen;
char log_file[PATH_MAX]; char log_file[PATH_MAX];
@ -1271,39 +1310,13 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
if (log_all_ref_updates < 0) if (log_all_ref_updates < 0)
log_all_ref_updates = !is_bare_repository(); log_all_ref_updates = !is_bare_repository();
git_snpath(log_file, sizeof(log_file), "logs/%s", ref_name); result = log_ref_setup(ref_name, log_file, sizeof(log_file));
if (result)
if (log_all_ref_updates && return result;
(!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)
return error("unable to create directory for %s",
log_file);
oflags |= O_CREAT;
}
logfd = open(log_file, 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)) {
return error("There are still logs under '%s'",
log_file);
}
logfd = open(log_file, oflags, 0666);
}
logfd = open(log_file, oflags);
if (logfd < 0) if (logfd < 0)
return error("Unable to append to %s: %s", return 0;
log_file, strerror(errno));
}
adjust_shared_perm(log_file);
msglen = msg ? strlen(msg) : 0; msglen = msg ? strlen(msg) : 0;
committer = git_committer_info(0); committer = git_committer_info(0);
maxlen = strlen(committer) + msglen + 100; maxlen = strlen(committer) + msglen + 100;

3
refs.h
View File

@ -68,6 +68,9 @@ extern void unlock_ref(struct ref_lock *lock);
/** Writes sha1 into the ref specified by the 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); 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. **/ /** 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); 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);

View File

@ -49,6 +49,62 @@ test_expect_success '--orphan must be rejected with -b' '
test refs/heads/master = "$(git symbolic-ref HEAD)" 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' ' test_expect_success '--orphan is rejected with an existing name' '
git checkout master && git checkout master &&
test_must_fail git checkout --orphan 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 && git reset --hard &&
echo local >>"$TEST_FILE" && echo local >>"$TEST_FILE" &&
cat "$TEST_FILE" >"$TEST_FILE.saved" && 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 refs/heads/master = "$(git symbolic-ref HEAD)" &&
test_cmp "$TEST_FILE" "$TEST_FILE.saved" && test_cmp "$TEST_FILE" "$TEST_FILE.saved" &&
git diff-index --quiet --cached HEAD && git diff-index --quiet --cached HEAD &&
git reset --hard 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 test_done

View File

@ -224,6 +224,30 @@ test_expect_success \
test -f .git/logs/refs/heads/g/h/i && test -f .git/logs/refs/heads/g/h/i &&
diff expect .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' ' test_expect_success 'avoid ambiguous track' '
git config branch.autosetupmerge true && git config branch.autosetupmerge true &&
git config remote.ambi1.url lalala && git config remote.ambi1.url lalala &&