mirror of
https://github.com/git/git
synced 2024-08-24 18:26:02 +00:00
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:
commit
5bebcd4ecb
|
@ -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::
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -842,7 +842,7 @@ _git_checkout ()
|
|||
--*)
|
||||
__gitcomp "
|
||||
--quiet --ours --theirs --track --no-track --merge
|
||||
--conflict= --patch
|
||||
--conflict= --orphan --patch
|
||||
"
|
||||
;;
|
||||
*)
|
||||
|
|
79
refs.c
79
refs.c
|
@ -1258,10 +1258,49 @@ static int copy_msg(char *buf, const char *msg)
|
|||
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,
|
||||
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;
|
||||
int msglen;
|
||||
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)
|
||||
log_all_ref_updates = !is_bare_repository();
|
||||
|
||||
git_snpath(log_file, sizeof(log_file), "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)
|
||||
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);
|
||||
}
|
||||
|
||||
if (logfd < 0)
|
||||
return error("Unable to append to %s: %s",
|
||||
log_file, strerror(errno));
|
||||
}
|
||||
|
||||
adjust_shared_perm(log_file);
|
||||
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;
|
||||
|
|
3
refs.h
3
refs.h
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 &&
|
||||
|
|
Loading…
Reference in a new issue