--- /dev/null
+Git v2.6.5 Release Notes
+========================
+
+Fixes since v2.6.4
+------------------
+
+ * Because "test_when_finished" in our test framework queues the
+ clean-up tasks to be done in a shell variable, it should not be
+ used inside a subshell. Add a mechanism to allow 'bash' to catch
+ such uses, and fix the ones that were found.
+
+ * Update "git subtree" (in contrib/) so that it can take whitespaces
+ in the pathnames, not only in the in-tree pathname but the name of
+ the directory that the repository is in.
+
+ * Cosmetic improvement to lock-file error messages.
+
+ * mark_tree_uninteresting() has code to handle the case where it gets
+ passed a NULL pointer in its 'tree' parameter, but the function had
+ 'object = &tree->object' assignment before checking if tree is
+ NULL. This gives a compiler an excuse to declare that tree will
+ never be NULL and apply a wrong optimization. Avoid it.
+
+ * The helper used to iterate over loose object directories to prune
+ stale objects did not closedir() immediately when it is done with a
+ directory--a callback such as the one used for "git prune" may want
+ to do rmdir(), but it would fail on open directory on platforms
+ such as WinXP.
+
+ * "git p4" used to import Perforce CLs that touch only paths outside
+ the client spec as empty commits. It has been corrected to ignore
+ them instead, with a new configuration git-p4.keepEmptyCommits as a
+ backward compatibility knob.
+
+ * The exit code of git-fsck did not reflect some types of errors
+ found in packed objects, which has been corrected.
+
+ * The completion script (in contrib/) used to list "git column"
+ (which is not an end-user facing command) as one of the choices
+
+ * Improve error reporting when SMTP TLS fails.
+
+ * When getpwuid() on the system returned NULL (e.g. the user is not
+ in the /etc/passwd file or other uid-to-name mappings), the
+ codepath to find who the user is to record it in the reflog barfed
+ and died. Loosen the check in this codepath, which already accepts
+ questionable ident string (e.g. host part of the e-mail address is
+ obviously bogus), and in general when we operate fmt_ident() function
+ in non-strict mode.
+
+ * "git symbolic-ref" forgot to report a failure with its exit status.
+
+ * History traversal with "git log --source" that starts with an
+ annotated tag failed to report the tag as "source", due to an
+ old regression in the command line parser back in v2.2 days.
+
+Also contains typofixes, documentation updates and trivial code
+clean-ups.
These rules make it easy for shell script based tools to parse
reference names, pathname expansion by the shell when a reference name is used
-unquoted (by mistake), and also avoids ambiguities in certain
+unquoted (by mistake), and also avoid ambiguities in certain
reference name expressions (see linkgit:gitrevisions[7]):
. A double-dot `..` is often used as in `ref1..ref2`, and in some
option '--use-client-spec'. See the "CLIENT SPEC" section above.
This variable is a boolean, not the name of a p4 client.
+git-p4.keepEmptyCommits::
+ A changelist that contains only excluded files will be imported
+ as an empty commit if this boolean option is set to true.
+
Submit variables
~~~~~~~~~~~~~~~~
git-p4.detectRenames::
+
"git submodule sync" synchronizes all submodules while
"git submodule sync \-- A" synchronizes submodule "A" only.
++
+If `--recursive` is specified, this command will recurse into the
+registered submodules, and sync any nested submodules within.
OPTIONS
-------
for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.
--recursive::
- This option is only valid for foreach, update and status commands.
+ This option is only valid for foreach, update, status and sync commands.
Traverse submodules recursively. The operation is performed not
only in the submodules of the current repo, but also
in any nested submodules inside those submodules (and so on).
[--[no-]assume-unchanged]
[--[no-]skip-worktree]
[--ignore-submodules]
+ [--[no-]split-index]
[--[no-|force-]untracked-cache]
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v2.6.4/git.html[documentation for release 2.6.4]
+* link:v2.6.5/git.html[documentation for release 2.6.5]
* release notes for
+ link:RelNotes/2.6.5.txt[2.6.5],
link:RelNotes/2.6.4.txt[2.6.4],
link:RelNotes/2.6.3.txt[2.6.3],
link:RelNotes/2.6.2.txt[2.6.2],
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.6.4
+DEF_VER=v2.6.5
LF='
'
-Documentation/RelNotes/2.6.4.txt
\ No newline at end of file
+Documentation/RelNotes/2.6.5.txt
\ No newline at end of file
static void find_merge_parents(struct merge_parents *result,
struct strbuf *in, unsigned char *head)
{
- struct commit_list *parents, *next;
+ struct commit_list *parents;
struct commit *head_commit;
int pos = 0, i, j;
parents = reduce_heads(parents);
while (parents) {
+ struct commit *cmit = pop_commit(&parents);
for (i = 0; i < result->nr; i++)
- if (!hashcmp(result->item[i].commit,
- parents->item->object.sha1))
+ if (!hashcmp(result->item[i].commit, cmit->object.sha1))
result->item[i].used = 1;
- next = parents->next;
- free(parents);
- parents = next;
}
for (i = j = 0; i < result->nr; i++) {
int *head_subsumed,
struct commit_list *remoteheads)
{
- struct commit_list *parents, *next, **remotes = &remoteheads;
+ struct commit_list *parents, **remotes;
/*
* Is the current HEAD reachable from another commit being
/* Find what parents to record by checking independent ones. */
parents = reduce_heads(remoteheads);
- for (remoteheads = NULL, remotes = &remoteheads;
- parents;
- parents = next) {
- struct commit *commit = parents->item;
- next = parents->next;
+ remoteheads = NULL;
+ remotes = &remoteheads;
+ while (parents) {
+ struct commit *commit = pop_commit(&parents);
if (commit == head_commit)
*head_subsumed = 0;
else
remotes = &commit_list_insert(commit, remotes)->next;
- free(parents);
}
return remoteheads;
}
*/
static void mark_reachable(struct expire_reflog_policy_cb *cb)
{
- struct commit *commit;
struct commit_list *pending;
unsigned long expire_limit = cb->mark_limit;
struct commit_list *leftover = NULL;
pending = cb->mark_list;
while (pending) {
- struct commit_list *entry = pending;
struct commit_list *parent;
- pending = entry->next;
- commit = entry->item;
- free(entry);
+ struct commit *commit = pop_commit(&pending);
if (commit->object.flags & REACHABLE)
continue;
if (parse_commit(commit))
b = lookup_commit_reference(end);
exclude = get_merge_bases(a, b);
while (exclude) {
- struct commit_list *n = exclude->next;
- show_rev(REVERSED,
- exclude->item->object.sha1,NULL);
- free(exclude);
- exclude = n;
+ struct commit *commit = pop_commit(&exclude);
+ show_rev(REVERSED, commit->object.sha1, NULL);
}
}
*dotdot = '.';
#include "refs.h"
#include "builtin.h"
#include "color.h"
+#include "argv-array.h"
#include "parse-options.h"
static const char* show_branch_usage[] = {
static int showbranch_use_color = -1;
-static int default_num;
-static int default_alloc;
-static const char **default_arg;
+static struct argv_array default_args = ARGV_ARRAY_INIT;
#define UNINTERESTING 01
return NULL;
}
-static struct commit *pop_one_commit(struct commit_list **list_p)
-{
- struct commit *commit;
- struct commit_list *list;
- list = *list_p;
- commit = list->item;
- *list_p = list->next;
- free(list);
- return commit;
-}
-
struct commit_name {
const char *head_name; /* which head's ancestor? */
int generation; /* how many parents away from head_name */
while (*list_p) {
struct commit_list *parents;
int still_interesting = !!interesting(*list_p);
- struct commit *commit = pop_one_commit(list_p);
+ struct commit *commit = pop_commit(list_p);
int flags = commit->object.flags & all_mask;
if (!still_interesting && extra <= 0)
int exit_status = 1;
while (seen) {
- struct commit *commit = pop_one_commit(&seen);
+ struct commit *commit = pop_commit(&seen);
int flags = commit->object.flags & all_mask;
if (!(flags & UNINTERESTING) &&
((flags & all_revs) == all_revs)) {
* default_arg is now passed to parse_options(), so we need to
* mimic the real argv a bit better.
*/
- if (!default_num) {
- default_alloc = 20;
- default_arg = xcalloc(default_alloc, sizeof(*default_arg));
- default_arg[default_num++] = "show-branch";
- } else if (default_alloc <= default_num + 1) {
- default_alloc = default_alloc * 3 / 2 + 20;
- REALLOC_ARRAY(default_arg, default_alloc);
- }
- default_arg[default_num++] = xstrdup(value);
- default_arg[default_num] = NULL;
+ if (!default_args.argc)
+ argv_array_push(&default_args, "show-branch");
+ argv_array_push(&default_args, value);
return 0;
}
git_config(git_show_branch_config, NULL);
/* If nothing is specified, try the default first */
- if (ac == 1 && default_num) {
- ac = default_num;
- av = default_arg;
+ if (ac == 1 && default_args.argc) {
+ ac = default_args.argc;
+ av = default_args.argv;
}
ac = parse_options(ac, av, prefix, builtin_show_branch_options,
all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
while (seen) {
- struct commit *commit = pop_one_commit(&seen);
+ struct commit *commit = pop_commit(&seen);
int this_flag = commit->object.flags;
int is_merge_point = ((this_flag & all_revs) == all_revs);
if (!strcmp(argv[0], "HEAD") &&
!starts_with(argv[1], "refs/"))
die("Refusing to point HEAD outside of refs/");
- create_symref(argv[0], argv[1], msg);
+ ret = !!create_symref(argv[0], argv[1], msg);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
void free_commit_list(struct commit_list *list)
{
- while (list) {
- struct commit_list *temp = list;
- list = temp->next;
- free(temp);
- }
+ while (list)
+ pop_commit(&list);
}
struct commit_list * commit_list_insert_by_date(struct commit *item, struct commit_list **list)
struct commit *pop_most_recent_commit(struct commit_list **list,
unsigned int mark)
{
- struct commit *ret = (*list)->item;
+ struct commit *ret = pop_commit(list);
struct commit_list *parents = ret->parents;
- struct commit_list *old = *list;
-
- *list = (*list)->next;
- free(old);
while (parents) {
struct commit *commit = parents->item;
list = paint_down_to_common(one, n, twos);
while (list) {
- struct commit_list *next = list->next;
- if (!(list->item->object.flags & STALE))
- commit_list_insert_by_date(list->item, &result);
- free(list);
- list = next;
+ struct commit *commit = pop_commit(&list);
+ if (!(commit->object.flags & STALE))
+ commit_list_insert_by_date(commit, &result);
}
return result;
}
* if everything else stays the same.
*/
while (parents) {
- struct commit_list *next = parents->next;
- struct commit *parent = parents->item;
-
+ struct commit *parent = pop_commit(&parents);
strbuf_addf(&buffer, "parent %s\n",
sha1_to_hex(parent->object.sha1));
- free(parents);
- parents = next;
}
/* Person/date information */
}
if (commit_lock_file(lock) < 0) {
- error("could not commit config file %s", config_filename);
+ error("could not write config file %s: %s", config_filename,
+ strerror(errno));
ret = CONFIG_NO_WRITE;
lock = NULL;
goto out_free;
fclose(config_file);
unlock_and_out:
if (commit_lock_file(lock) < 0)
- ret = error("could not commit config file %s", config_filename);
+ ret = error("could not write config file %s: %s",
+ config_filename, strerror(errno));
out:
free(filename_buf);
return ret;
check-mailmap) : plumbing;;
check-ref-format) : plumbing;;
checkout-index) : plumbing;;
+ column) : internal helper;;
commit-tree) : plumbing;;
count-objects) : infrequent;;
credential) : credentials;;
debug "Merging split branch into HEAD..."
latest_old=$(cache_get latest_old)
git merge -s ours \
- -m "$(rejoin_msg $dir $latest_old $latest_new)" \
+ -m "$(rejoin_msg "$dir" $latest_old $latest_new)" \
$latest_new >&2 || exit $?
fi
if [ -n "$branch" ]; then
refspec=$2
echo "git push using: " $repository $refspec
localrev=$(git subtree split --prefix="$prefix") || die
- git push $repository $localrev:refs/heads/$refspec
+ git push "$repository" $localrev:refs/heads/$refspec
else
die "'$dir' must already exist. Try 'git subtree add'."
fi
#!/bin/sh
#
# Copyright (c) 2012 Avery Pennaraum
+# Copyright (c) 2015 Alexey Shumkin
#
test_description='Basic porcelain support for subtrees
fi
}
-fixnl()
-{
- t=""
- while read x; do
- t="$t$x "
- done
- echo $t
-}
-
-multiline()
-{
- while read x; do
- set -- $x
- for d in "$@"; do
- echo "$d"
- done
- done
-}
-
undo()
{
git reset --hard HEAD~
}
test_expect_success 'init subproj' '
- test_create_repo subproj
+ test_create_repo "sub proj"
'
# To the subproject!
-cd subproj
+cd ./"sub proj"
test_expect_success 'add sub1' '
create sub1 &&
'
test_expect_success 'fetch subproj history' '
- git fetch ./subproj sub1 &&
+ git fetch ./"sub proj" sub1 &&
git branch sub1 FETCH_HEAD
'
test_expect_success 'no subtree exists in main tree' '
- test_must_fail git subtree merge --prefix=subdir sub1
+ test_must_fail git subtree merge --prefix="sub dir" sub1
'
test_expect_success 'no pull from non-existant subtree' '
- test_must_fail git subtree pull --prefix=subdir ./subproj sub1
+ test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" sub1
'
test_expect_success 'check if --message works for add' '
- git subtree add --prefix=subdir --message="Added subproject" sub1 &&
+ git subtree add --prefix="sub dir" --message="Added subproject" sub1 &&
check_equal ''"$(last_commit_message)"'' "Added subproject" &&
undo
'
test_expect_success 'check if --message works as -m and --prefix as -P' '
- git subtree add -P subdir -m "Added subproject using git subtree" sub1 &&
+ git subtree add -P "sub dir" -m "Added subproject using git subtree" sub1 &&
check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" &&
undo
'
test_expect_success 'check if --message works with squash too' '
- git subtree add -P subdir -m "Added subproject with squash" --squash sub1 &&
+ git subtree add -P "sub dir" -m "Added subproject with squash" --squash sub1 &&
check_equal ''"$(last_commit_message)"'' "Added subproject with squash" &&
undo
'
test_expect_success 'add subproj to mainline' '
- git subtree add --prefix=subdir/ FETCH_HEAD &&
- check_equal ''"$(last_commit_message)"'' "Add '"'subdir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
+ git subtree add --prefix="sub dir"/ FETCH_HEAD &&
+ check_equal ''"$(last_commit_message)"'' "Add '"'sub dir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'"
'
# this shouldn't actually do anything, since FETCH_HEAD is already a parent
'
test_expect_success 'add main-sub5' '
- create subdir/main-sub5 &&
+ create "sub dir/main-sub5" &&
git commit -m "main-sub5"
'
'
test_expect_success 'add main-sub7' '
- create subdir/main-sub7 &&
+ create "sub dir/main-sub7" &&
git commit -m "main-sub7"
'
test_expect_success 'fetch new subproj history' '
- git fetch ./subproj sub2 &&
+ git fetch ./"sub proj" sub2 &&
git branch sub2 FETCH_HEAD
'
test_expect_success 'check if --message works for merge' '
- git subtree merge --prefix=subdir -m "Merged changes from subproject" sub2 &&
+ git subtree merge --prefix="sub dir" -m "Merged changes from subproject" sub2 &&
check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" &&
undo
'
test_expect_success 'check if --message for merge works with squash too' '
- git subtree merge --prefix subdir -m "Merged changes from subproject using squash" --squash sub2 &&
+ git subtree merge --prefix "sub dir" -m "Merged changes from subproject using squash" --squash sub2 &&
check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" &&
undo
'
test_expect_success 'merge new subproj history into subdir' '
- git subtree merge --prefix=subdir FETCH_HEAD &&
+ git subtree merge --prefix="sub dir" FETCH_HEAD &&
git branch pre-split &&
check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" &&
undo
'
test_expect_success 'check if --message works for split+rejoin' '
- spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
git branch spl1 "$spl1" &&
check_equal ''"$(last_commit_message)"'' "Split & rejoin" &&
undo
'
test_expect_success 'check split with --branch' '
- spl1=$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
+ spl1=$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin) &&
undo &&
- git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr1 &&
+ git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr1 &&
check_equal ''"$(git rev-parse splitbr1)"'' "$spl1"
'
test_expect_success 'check hash of split' '
- spl1=$(git subtree split --prefix subdir) &&
- git subtree split --prefix subdir --branch splitbr1test &&
+ spl1=$(git subtree split --prefix "sub dir") &&
+ git subtree split --prefix "sub dir" --branch splitbr1test &&
check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" &&
new_hash=$(git rev-parse splitbr1test~2) &&
check_equal ''"$new_hash"'' "$subdir_hash"
'
test_expect_success 'check split with --branch for an existing branch' '
- spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
undo &&
git branch splitbr2 sub1 &&
- git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --branch splitbr2 &&
+ git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr2 &&
check_equal ''"$(git rev-parse splitbr2)"'' "$spl1"
'
test_expect_success 'check split with --branch for an incompatible branch' '
- test_must_fail git subtree split --prefix subdir --onto FETCH_HEAD --branch subdir
+ test_must_fail git subtree split --prefix "sub dir" --onto FETCH_HEAD --branch subdir
'
test_expect_success 'check split+rejoin' '
- spl1=''"$(git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
+ spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' &&
undo &&
- git subtree split --annotate='"'*'"' --prefix subdir --onto FETCH_HEAD --rejoin &&
- check_equal ''"$(last_commit_message)"'' "Split '"'"'subdir/'"'"' into commit '"'"'"$spl1"'"'"'"
+ git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --rejoin &&
+ check_equal ''"$(last_commit_message)"'' "Split '"'"'sub dir/'"'"' into commit '"'"'"$spl1"'"'"'"
'
test_expect_success 'add main-sub8' '
- create subdir/main-sub8 &&
+ create "sub dir/main-sub8" &&
git commit -m "main-sub8"
'
# To the subproject!
-cd ./subproj
+cd ./"sub proj"
test_expect_success 'merge split into subproj' '
git fetch .. spl1 &&
cd ..
test_expect_success 'split for sub8' '
- split2=''"$(git subtree split --annotate='"'*'"' --prefix subdir/ --rejoin)"'' &&
+ split2=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir/" --rejoin)"'' &&
git branch split2 "$split2"
'
test_expect_success 'add main-sub10' '
- create subdir/main-sub10 &&
+ create "sub dir/main-sub10" &&
git commit -m "main-sub10"
'
test_expect_success 'split for sub10' '
- spl3=''"$(git subtree split --annotate='"'*'"' --prefix subdir --rejoin)"'' &&
+ spl3=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --rejoin)"'' &&
git branch spl3 "$spl3"
'
# To the subproject!
-cd ./subproj
+cd ./"sub proj"
test_expect_success 'merge split into subproj' '
git fetch .. spl3 &&
git branch subproj-merge-spl3
'
-chkm="main4 main6"
-chkms="main-sub10 main-sub5 main-sub7 main-sub8"
-chkms_sub=$(echo $chkms | multiline | sed 's,^,subdir/,' | fixnl)
-chks="sub1 sub2 sub3 sub9"
-chks_sub=$(echo $chks | multiline | sed 's,^,subdir/,' | fixnl)
+chkm="main4
+main6"
+chkms="main-sub10
+main-sub5
+main-sub7
+main-sub8"
+chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chkms
+TXT
+)
+chks="sub1
+sub2
+sub3
+sub9"
+chks_sub=$(cat <<TXT | sed 's,^,sub dir/,'
+$chks
+TXT
+)
test_expect_success 'make sure exactly the right set of files ends up in the subproj' '
- subfiles=''"$(git ls-files | fixnl)"'' &&
- check_equal "$subfiles" "$chkms $chks"
+ subfiles="$(git ls-files)" &&
+ check_equal "$subfiles" "$chkms
+$chks"
'
-
test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' '
- allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
- check_equal "$allchanges" "$chkms $chks"
+ allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+ check_equal "$allchanges" "$chkms
+$chks"
'
# Back to mainline
cd ..
test_expect_success 'pull from subproj' '
- git fetch ./subproj subproj-merge-spl3 &&
+ git fetch ./"sub proj" subproj-merge-spl3 &&
git branch subproj-merge-spl3 FETCH_HEAD &&
- git subtree pull --prefix=subdir ./subproj subproj-merge-spl3
+ git subtree pull --prefix="sub dir" ./"sub proj" subproj-merge-spl3
'
test_expect_success 'make sure exactly the right set of files ends up in the mainline' '
- mainfiles=''"$(git ls-files | fixnl)"'' &&
- check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub"
+ mainfiles=$(git ls-files) &&
+ check_equal "$mainfiles" "$chkm
+$chkms_sub
+$chks_sub"
'
test_expect_success 'make sure each filename changed exactly once in the entire history' '
# main-sub?? and /subdir/main-sub?? both change, because those are the
# changes that were split into their own history. And subdir/sub?? never
# change, since they were *only* changed in the subtree branch.
- allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | fixnl)"'' &&
- check_equal "$allchanges" ''"$(echo $chkms $chkm $chks $chkms_sub | multiline | sort | fixnl)"''
+ allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' &&
+ check_equal "$allchanges" ''"$(cat <<TXT | sort
+$chkms
+$chkm
+$chks
+$chkms_sub
+TXT
+)"''
'
test_expect_success 'make sure the --rejoin commits never make it into subproj' '
test_expect_success 'add sub as subdir in main' '
git fetch ../sub master &&
git branch sub2 FETCH_HEAD &&
- git subtree add --prefix subdir sub2
+ git subtree add --prefix "sub dir" sub2
'
cd ../sub
test_expect_success 'merge from sub' '
git fetch ../sub master &&
git branch sub3 FETCH_HEAD &&
- git subtree merge --prefix subdir sub3
+ git subtree merge --prefix "sub dir" sub3
'
test_expect_success 'add main-sub4' '
- create subdir/main-sub4 &&
+ create "sub dir/main-sub4" &&
git commit -m "main-sub4"
'
test_expect_success 'split for main-sub4 without --onto' '
- git subtree split --prefix subdir --branch mainsub4
+ git subtree split --prefix "sub dir" --branch mainsub4
'
# at this point, the new commit parent should be sub3 if it is not,
))
'
+# test push
+
+cd ../..
+
+mkdir test-push
+
+cd test-push
+
+test_expect_success 'init main' '
+ test_create_repo main
+'
+
+test_expect_success 'init sub' '
+ test_create_repo "sub project"
+'
+
+cd ./"sub project"
+
+test_expect_success 'add subproject' '
+ create "sub project" &&
+ git commit -m "Sub project: 1" &&
+ git branch sub-branch-1
+'
+
+cd ../main
+
+test_expect_success 'make first commit and add subproject' '
+ create "main-1" &&
+ git commit -m "main: 1" &&
+ git subtree add "../sub project" --prefix "sub dir" --message "Added subproject" sub-branch-1 &&
+ check_equal "$(last_commit_message)" "Added subproject"
+'
+
+test_expect_success 'make second commit to a subproject file and push it into a sub project' '
+ create "sub dir/sub1" &&
+ git commit -m "Sub project: 2" &&
+ git subtree push "../sub project" --prefix "sub dir" sub-branch-1
+'
+
+cd ../"sub project"
+
+test_expect_success 'Test second commit is pushed' '
+ git checkout sub-branch-1 &&
+ check_equal "$(last_commit_message)" "Sub project: 2"
+'
+
test_done
print_line(extra);
parse_credential_file(fn, c, NULL, print_line);
if (commit_lock_file(&credential_lock) < 0)
- die_errno("unable to commit credential store");
+ die_errno("unable to write credential store");
}
static void store_credential_file(const char *fn, struct credential *c)
dump_marks_helper(f, 0, marks);
if (commit_lock_file(&mark_lock)) {
- failure |= error("Unable to commit marks file %s: %s",
+ failure |= error("Unable to write file %s: %s",
export_marks_file, strerror(errno));
return;
}
/* Warn on an inaccessible file that ought to be accessible */
void warn_on_inaccessible(const char *path);
-/* Get the passwd entry for the UID of the current process. */
-struct passwd *xgetpwuid_self(void);
-
#ifdef GMTIME_UNRELIABLE_ERRORS
struct tm *git_gmtime(const time_t *);
struct tm *git_gmtime_r(const time_t *, struct tm *);
filesToDelete = []
for f in files:
- # if using a client spec, only add the files that have
- # a path in the client
- if self.clientSpecDirs:
- if self.clientSpecDirs.map_in_client(f['path']) == "":
- continue
-
filesForCommit.append(f)
if f['action'] in self.delete_actions:
filesToDelete.append(f)
gitStream.write(description)
gitStream.write("\n")
+ def inClientSpec(self, path):
+ if not self.clientSpecDirs:
+ return True
+ inClientSpec = self.clientSpecDirs.map_in_client(path)
+ if not inClientSpec and self.verbose:
+ print('Ignoring file outside of client spec: {0}'.format(path))
+ return inClientSpec
+
+ def hasBranchPrefix(self, path):
+ if not self.branchPrefixes:
+ return True
+ hasPrefix = [p for p in self.branchPrefixes
+ if p4PathStartsWith(path, p)]
+ if hasPrefix and self.verbose:
+ print('Ignoring file outside of prefix: {0}'.format(path))
+ return hasPrefix
+
def commit(self, details, files, branch, parent = ""):
epoch = details["time"]
author = details["user"]
if self.verbose:
- print "commit into %s" % branch
-
- # start with reading files; if that fails, we should not
- # create a commit.
- new_files = []
- for f in files:
- if [p for p in self.branchPrefixes if p4PathStartsWith(f['path'], p)]:
- new_files.append (f)
- else:
- sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path'])
+ print('commit into {0}'.format(branch))
if self.clientSpecDirs:
self.clientSpecDirs.update_client_spec_path_cache(files)
+ files = [f for f in files
+ if self.inClientSpec(f['path']) and self.hasBranchPrefix(f['path'])]
+
+ if not files and not gitConfigBool('git-p4.keepEmptyCommits'):
+ print('Ignoring revision {0} as it would produce an empty commit.'
+ .format(details['change']))
+ return
+
self.gitStream.write("commit %s\n" % branch)
self.gitStream.write("mark :%s\n" % details["change"])
self.committedChanges.add(int(details["change"]))
print "parent %s" % parent
self.gitStream.write("from %s\n" % parent)
- self.streamP4Files(new_files)
+ self.streamP4Files(files)
self.gitStream.write("\n")
change = int(details["change"])
require Net::SMTP::SSL;
$smtp_domain ||= maildomain();
require IO::Socket::SSL;
+
+ # Suppress "variable accessed once" warning.
+ {
+ no warnings 'once';
+ $IO::Socket::SSL::DEBUG = 1;
+ }
+
# Net::SMTP::SSL->new() does not forward any SSL options
IO::Socket::SSL::set_client_defaults(
ssl_verify_params());
static struct strbuf git_default_name = STRBUF_INIT;
static struct strbuf git_default_email = STRBUF_INIT;
static struct strbuf git_default_date = STRBUF_INIT;
+static int default_email_is_bogus;
+static int default_name_is_bogus;
#define IDENT_NAME_GIVEN 01
#define IDENT_MAIL_GIVEN 02
#define get_gecos(struct_passwd) ((struct_passwd)->pw_gecos)
#endif
+static struct passwd *xgetpwuid_self(int *is_bogus)
+{
+ struct passwd *pw;
+
+ errno = 0;
+ pw = getpwuid(getuid());
+ if (!pw) {
+ static struct passwd fallback;
+ fallback.pw_name = "unknown";
+#ifndef NO_GECOS_IN_PWENT
+ fallback.pw_gecos = "Unknown";
+#endif
+ pw = &fallback;
+ if (is_bogus)
+ *is_bogus = 1;
+ }
+ return pw;
+}
+
static void copy_gecos(const struct passwd *w, struct strbuf *name)
{
char *src;
return 0;
}
-static void add_domainname(struct strbuf *out)
+static int canonical_name(const char *host, struct strbuf *out)
+{
+ int status = -1;
+
+#ifndef NO_IPV6
+ struct addrinfo hints, *ai;
+ memset (&hints, '\0', sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ if (!getaddrinfo(host, NULL, &hints, &ai)) {
+ if (ai && strchr(ai->ai_canonname, '.')) {
+ strbuf_addstr(out, ai->ai_canonname);
+ status = 0;
+ }
+ freeaddrinfo(ai);
+ }
+#else
+ struct hostent *he = gethostbyname(host);
+ if (he && strchr(he->h_name, '.')) {
+ strbuf_addstr(out, he->h_name);
+ status = 0;
+ }
+#endif /* NO_IPV6 */
+
+ return status;
+}
+
+static void add_domainname(struct strbuf *out, int *is_bogus)
{
char buf[1024];
- struct hostent *he;
if (gethostname(buf, sizeof(buf))) {
warning("cannot get host name: %s", strerror(errno));
strbuf_addstr(out, "(none)");
+ *is_bogus = 1;
return;
}
if (strchr(buf, '.'))
strbuf_addstr(out, buf);
- else if ((he = gethostbyname(buf)) && strchr(he->h_name, '.'))
- strbuf_addstr(out, he->h_name);
- else
+ else if (canonical_name(buf, out) < 0) {
strbuf_addf(out, "%s.(none)", buf);
+ *is_bogus = 1;
+ }
}
-static void copy_email(const struct passwd *pw, struct strbuf *email)
+static void copy_email(const struct passwd *pw, struct strbuf *email,
+ int *is_bogus)
{
/*
* Make up a fake email address
if (!add_mailname_host(email))
return; /* read from "/etc/mailname" (Debian) */
- add_domainname(email);
+ add_domainname(email, is_bogus);
}
const char *ident_default_name(void)
{
if (!git_default_name.len) {
- copy_gecos(xgetpwuid_self(), &git_default_name);
+ copy_gecos(xgetpwuid_self(&default_name_is_bogus), &git_default_name);
strbuf_trim(&git_default_name);
}
return git_default_name.buf;
committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
} else
- copy_email(xgetpwuid_self(), &git_default_email);
+ copy_email(xgetpwuid_self(&default_email_is_bogus),
+ &git_default_email, &default_email_is_bogus);
strbuf_trim(&git_default_email);
}
return git_default_email.buf;
fputs(env_hint, stderr);
die("empty ident name (for <%s>) not allowed", email);
}
- pw = xgetpwuid_self();
+ pw = xgetpwuid_self(NULL);
name = pw->pw_name;
}
- if (strict && email == git_default_email.buf &&
- strstr(email, "(none)")) {
+ if (want_name && strict &&
+ name == git_default_name.buf && default_name_is_bogus) {
+ fputs(env_hint, stderr);
+ die("unable to auto-detect name (got '%s')", name);
+ }
+
+ if (strict && email == git_default_email.buf && default_email_is_bogus) {
fputs(env_hint, stderr);
die("unable to auto-detect email address (got '%s')", email);
}
sha1_to_hex(entries[i].sha1), p->pack_name);
else if (fn) {
int eaten = 0;
- fn(entries[i].sha1, type, size, data, &eaten);
+ err |= fn(entries[i].sha1, type, size, data, &eaten);
if (eaten)
data = NULL;
}
get_lock_file_path(lock->lk));
rollback_lock_file(&reflog_lock);
} else if (commit_lock_file(&reflog_lock)) {
- status |= error("unable to commit reflog '%s' (%s)",
+ status |= error("unable to write reflog %s: %s",
log_file, strerror(errno));
} else if (update && commit_ref(lock)) {
status |= error("couldn't set %s", lock->ref_name);
static void unmark_and_free(struct commit_list *list, unsigned int mark)
{
while (list) {
- struct commit_list *temp = list;
- temp->item->object.flags &= ~mark;
- list = temp->next;
- free(temp);
+ struct commit *commit = pop_commit(&list);
+ commit->object.flags &= ~mark;
}
}
void mark_tree_uninteresting(struct tree *tree)
{
- struct object *obj = &tree->object;
+ struct object *obj;
if (!tree)
return;
+
+ obj = &tree->object;
if (obj->flags & UNINTERESTING)
return;
obj->flags |= UNINTERESTING;
commit_list_insert(l->item, &parents);
while (parents) {
- struct commit *commit = parents->item;
- l = parents;
- parents = parents->next;
- free(l);
+ struct commit *commit = pop_commit(&parents);
while (commit) {
/*
/*
* We'll handle the tagged object by looping or dropping
* through to the non-tag handlers below. Do not
- * propagate data from the tag's pending entry.
+ * propagate path data from the tag's pending entry.
*/
- name = "";
path = NULL;
mode = 0;
}
}
while (list) {
- struct commit_list *entry = list;
- struct commit *commit = list->item;
+ struct commit *commit = pop_commit(&list);
struct object *obj = &commit->object;
show_early_output_fn_t show;
- list = list->next;
- free(entry);
-
if (commit == interesting_cache)
interesting_cache = NULL;
yet_to_do = NULL;
tail = &yet_to_do;
while (list) {
- commit = list->item;
- next = list->next;
- free(list);
- list = next;
+ commit = pop_commit(&list);
tail = simplify_one(revs, commit, tail);
}
}
while (list) {
struct merge_simplify_state *st;
- commit = list->item;
- next = list->next;
- free(list);
- list = next;
+ commit = pop_commit(&list);
st = locate_simplify_state(revs, commit);
if (st->simplified == commit)
tail = &commit_list_insert(commit, tail)->next;
return NULL;
do {
- struct commit_list *entry = revs->commits;
- struct commit *commit = entry->item;
-
- revs->commits = entry->next;
- free(entry);
+ struct commit *commit = pop_commit(&revs->commits);
if (revs->reflog_info) {
save_parents(revs, commit);
break;
}
}
- strbuf_setlen(path, baselen);
+ closedir(dir);
+ strbuf_setlen(path, baselen);
if (!r && subdir_cb)
r = subdir_cb(subdir_nr, path->buf, data);
- closedir(dir);
return r;
}
commit_list_insert(c, &head);
while (head) {
struct commit_list *p;
- struct commit *c = head->item;
+ struct commit *c = pop_commit(&head);
uint32_t **refs = ref_bitmap_at(&info->ref_bitmap, c);
- p = head;
- head = head->next;
- free(p);
-
/* XXX check "UNINTERESTING" from pack bitmaps if available */
if (c->object.flags & (SEEN | UNINTERESTING))
continue;
'
reset_to_sane
+test_expect_success 'symbolic-ref reports failure in exit code' '
+ test_when_finished "rm -f .git/HEAD.lock" &&
+ >.git/HEAD.lock &&
+ test_must_fail git symbolic-ref HEAD refs/heads/whatever
+'
+
+test_expect_success 'symbolic-ref writes reflog entry' '
+ git checkout -b log1 &&
+ test_commit one &&
+ git checkout -b log2 &&
+ test_commit two &&
+ git checkout --orphan orphan &&
+ git symbolic-ref -m create HEAD refs/heads/log1 &&
+ git symbolic-ref -m update HEAD refs/heads/log2 &&
+ cat >expect <<-\EOF &&
+ update
+ create
+ EOF
+ git log --format=%gs -g >actual &&
+ test_cmp expect actual
+'
+
test_done
# Turn single spaces into space/tab mix
sed "1s/ / /g; 2s/ / /g; 3s/ / /g" "$1"
printf "\n\t# comment\n #more\n\t # comment\n"
- ) >$1.new
+ ) >"$1.new"
mv "$1.new" "$1"
EOF
test_set_editor "$(pwd)/add-indent.sh" &&
test_i18ngrep broken stderr
'
+test_expect_success 'set up --source tests' '
+ git checkout --orphan source-a &&
+ test_commit one &&
+ test_commit two &&
+ git checkout -b source-b HEAD^ &&
+ test_commit three
+'
+
+test_expect_success 'log --source paints branch names' '
+ cat >expect <<-\EOF &&
+ 09e12a9 source-b three
+ 8e393e1 source-a two
+ 1ac6c77 source-b one
+ EOF
+ git log --oneline --source source-a source-b >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --source paints tag names' '
+ git tag -m tagged source-tag &&
+ cat >expect <<-\EOF &&
+ 09e12a9 source-tag three
+ 8e393e1 source-a two
+ 1ac6c77 source-tag one
+ EOF
+ git log --oneline --source source-tag source-a >actual &&
+ test_cmp expect actual
+'
+
test_done
sort >$(basename "$1")
}
-cmp_marks () {
- test_when_finished "rm -rf git.marks testgit.marks" &&
- clean_mark ".git/testgit/$1/git.marks" &&
- clean_mark ".git/testgit/$1/testgit.marks" &&
- test_cmp git.marks testgit.marks
-}
-
test_expect_success 'proper failure checks for fetching' '
(cd local &&
test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error &&
'
test_expect_success 'proper failure checks for pushing' '
+ test_when_finished "rm -rf local/git.marks local/testgit.marks" &&
(cd local &&
git checkout -b crash master &&
echo crash >>file &&
git commit -a -m crash &&
test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git push --all &&
- cmp_marks origin
+ clean_mark ".git/testgit/origin/git.marks" &&
+ clean_mark ".git/testgit/origin/testgit.marks" &&
+ test_cmp git.marks testgit.marks
)
'
'
test_expect_success 'mergetool merges all from subdir' '
+ test_config rerere.enabled false &&
(
cd subdir &&
- test_config rerere.enabled false &&
test_must_fail git merge master &&
( yes "r" | git mergetool ../submod ) &&
( yes "d" "d" | git mergetool --no-prompt ) &&
test_expect_success PERL 'difftool properly honors gitlink and core.worktree' '
git submodule add ./. submod/ule &&
+ test_config -C submod/ule diff.tool checktrees &&
+ test_config -C submod/ule difftool.checktrees.cmd '\''
+ test -d "$LOCAL" && test -d "$REMOTE" && echo good
+ '\'' &&
(
cd submod/ule &&
- test_config diff.tool checktrees &&
- test_config difftool.checktrees.cmd '\''
- test -d "$LOCAL" && test -d "$REMOTE" && echo good
- '\'' &&
echo good >expect &&
git difftool --tool=checktrees --dir-diff HEAD~ >actual &&
test_cmp expect actual
--- /dev/null
+#!/bin/sh
+
+test_description='Clone repositories and keep empty commits'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+ start_p4d
+'
+
+test_expect_success 'Create a repo' '
+ client_view "//depot/... //client/..." &&
+ (
+ cd "$cli" &&
+
+ mkdir -p subdir &&
+
+ >subdir/file1.txt &&
+ p4 add subdir/file1.txt &&
+ p4 submit -d "Add file 1" &&
+
+ >file2.txt &&
+ p4 add file2.txt &&
+ p4 submit -d "Add file 2" &&
+
+ >subdir/file3.txt &&
+ p4 add subdir/file3.txt &&
+ p4 submit -d "Add file 3" &&
+
+ >file4.txt &&
+ p4 add file4.txt &&
+ p4 submit -d "Add file 4" &&
+
+ p4 delete subdir/file3.txt &&
+ p4 submit -d "Remove file 3" &&
+
+ p4 delete file4.txt &&
+ p4 submit -d "Remove file 4"
+ )
+'
+
+test_expect_success 'Clone repo root path with all history' '
+ client_view "//depot/... //client/..." &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git init . &&
+ git p4 clone --use-client-spec --destination="$git" //depot@all &&
+ cat >expect <<-\EOF &&
+Remove file 4
+[git-p4: depot-paths = "//depot/": change = 6]
+
+Remove file 3
+[git-p4: depot-paths = "//depot/": change = 5]
+
+Add file 4
+[git-p4: depot-paths = "//depot/": change = 4]
+
+Add file 3
+[git-p4: depot-paths = "//depot/": change = 3]
+
+Add file 2
+[git-p4: depot-paths = "//depot/": change = 2]
+
+Add file 1
+[git-p4: depot-paths = "//depot/": change = 1]
+
+ EOF
+ git log --format=%B >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'Clone repo subdir with all history but keep empty commits' '
+ client_view "//depot/subdir/... //client/subdir/..." &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git init . &&
+ git config git-p4.keepEmptyCommits true &&
+ git p4 clone --use-client-spec --destination="$git" //depot@all &&
+ cat >expect <<-\EOF &&
+Remove file 4
+[git-p4: depot-paths = "//depot/": change = 6]
+
+Remove file 3
+[git-p4: depot-paths = "//depot/": change = 5]
+
+Add file 4
+[git-p4: depot-paths = "//depot/": change = 4]
+
+Add file 3
+[git-p4: depot-paths = "//depot/": change = 3]
+
+Add file 2
+[git-p4: depot-paths = "//depot/": change = 2]
+
+Add file 1
+[git-p4: depot-paths = "//depot/": change = 1]
+
+ EOF
+ git log --format=%B >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'Clone repo subdir with all history' '
+ client_view "//depot/subdir/... //client/subdir/..." &&
+ test_when_finished cleanup_git &&
+ (
+ cd "$git" &&
+ git init . &&
+ git p4 clone --use-client-spec --destination="$git" --verbose //depot@all &&
+ cat >expect <<-\EOF &&
+Remove file 3
+[git-p4: depot-paths = "//depot/": change = 5]
+
+Add file 3
+[git-p4: depot-paths = "//depot/": change = 3]
+
+Add file 1
+[git-p4: depot-paths = "//depot/": change = 1]
+
+ EOF
+ git log --format=%B >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'kill p4d' '
+ kill_p4d
+'
+
+test_done
# Unset a configuration variable, but don't fail if it doesn't exist.
test_unconfig () {
- git config --unset-all "$@"
+ config_dir=
+ if test "$1" = -C
+ then
+ shift
+ config_dir=$1
+ shift
+ fi
+ git ${config_dir:+-C "$config_dir"} config --unset-all "$@"
config_status=$?
case "$config_status" in
5) # ok, nothing to unset
# Set git config, automatically unsetting it after the test is over.
test_config () {
- test_when_finished "test_unconfig '$1'" &&
- git config "$@"
+ config_dir=
+ if test "$1" = -C
+ then
+ shift
+ config_dir=$1
+ shift
+ fi
+ test_when_finished "test_unconfig ${config_dir:+-C '$config_dir'} '$1'" &&
+ git ${config_dir:+-C "$config_dir"} config "$@"
}
test_config_global () {
# what went wrong.
test_when_finished () {
+ # We cannot detect when we are in a subshell in general, but by
+ # doing so on Bash is better than nothing (the test will
+ # silently pass on other shells).
+ test "${BASH_SUBSHELL-0}" = 0 ||
+ error "bug in test script: test_when_finished does nothing in a subshell"
test_cleanup="{ $*
} && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
}
commit_list_insert_by_date(want, &work);
while (work) {
- struct commit_list *list = work->next;
- struct commit *commit = work->item;
- free(work);
- work = list;
+ struct commit_list *list;
+ struct commit *commit = pop_commit(&work);
if (commit->object.flags & THEY_HAVE) {
want->object.flags |= COMMON_KNOWN;
return ret;
}
-struct passwd *xgetpwuid_self(void)
-{
- struct passwd *pw;
-
- errno = 0;
- pw = getpwuid(getuid());
- if (!pw)
- die(_("unable to look up current user in the passwd file: %s"),
- errno ? strerror(errno) : _("no such user"));
- return pw;
-}
-
char *xgetcwd(void)
{
struct strbuf sb = STRBUF_INIT;