mv: move submodules using a gitfile

When moving a submodule which uses a gitfile to point to the git directory stored in .git/modules/<name> of the superproject two changes must be made to make the submodule work: the .git file and the core.worktree setting must be adjusted to point from work tree to git directory and back. Achieve that by remembering which submodule uses a gitfile by storing the result of read_gitfile() of each submodule. If that is not NULL the new function connect_work_tree_and_git_dir() is called after renaming the submodule's work tree which updates the two settings to the new values. Extend the man page to inform the user about that feature (and while at it change the description to not talk about a script anymore, as mv is a builtin for quite some time now). Signed-off-by: Jens Lehmann <Jens.Lehmann@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jens Lehmann committed Jul 30, 2013 at 21:50 UTC a88c915de9886fe17005a5daff4900ced0ea76ad
5 files changed +73 -5
Documentation/git-mv.txt
+7 -1
@@ -13,7 +13,7 @@ SYNOPSIS
13
14 DESCRIPTION
15 -----------
16 -This script is used to move or rename a file, directory or symlink.
16 +Move or rename a file, directory or symlink.
17
18 git mv [-v] [-f] [-n] [-k] <source> <destination>
19 git mv [-v] [-f] [-n] [-k] <source> ... <destination directory>
@@ -44,6 +44,12 @@ OPTIONS
44 --verbose::
45 Report the names of files as they are moved.
46
47 +SUBMODULES
48 +----------
49 +Moving a submodule using a gitfile (which means they were cloned
50 +with a Git version 1.7.8 or newer) will update the gitfile and
51 +core.worktree setting to make the submodule work in the new location.
52 +
53 GIT
54 ---
55 Part of the linkgit:git[1] suite
builtin/mv.c
+15 -4
@@ -9,6 +9,7 @@
9 #include "cache-tree.h"
10 #include "string-list.h"
11 #include "parse-options.h"
12 +#include "submodule.h"
13
14 static const char * const builtin_mv_usage[] = {
15 N_("git mv [options] <source>... <destination>"),
@@ -66,7 +67,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
67 OPT_BOOLEAN('k', NULL, &ignore_errors, N_("skip move/rename errors")),
68 OPT_END(),
69 };
69 - const char **source, **destination, **dest_path;
70 + const char **source, **destination, **dest_path, **submodule_gitfile;
71 enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
72 struct stat st;
73 struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
@@ -85,6 +86,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
86 source = internal_copy_pathspec(prefix, argv, argc, 0);
87 modes = xcalloc(argc, sizeof(enum update_mode));
88 dest_path = internal_copy_pathspec(prefix, argv + argc, 1, 0);
89 + submodule_gitfile = xcalloc(argc, sizeof(char *));
90
91 if (dest_path[0][0] == '\0')
92 /* special case: "." was normalized to "" */
@@ -120,8 +122,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
122 else if (src_is_dir) {
123 int first = cache_name_pos(src, length);
124 if (first >= 0) {
125 + struct strbuf submodule_dotgit = STRBUF_INIT;
126 if (!S_ISGITLINK(active_cache[first]->ce_mode))
127 die (_("Huh? Directory %s is in index and no submodule?"), src);
128 + strbuf_addf(&submodule_dotgit, "%s/.git", src);
129 + submodule_gitfile[i] = read_gitfile(submodule_dotgit.buf);
130 + if (submodule_gitfile[i])
131 + submodule_gitfile[i] = xstrdup(submodule_gitfile[i]);
132 + strbuf_release(&submodule_dotgit);
133 } else {
134 const char *src_w_slash = add_slash(src);
135 int last, len_w_slash = length + 1;
@@ -216,9 +224,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
224 int pos;
225 if (show_only || verbose)
226 printf(_("Renaming %s to %s\n"), src, dst);
219 - if (!show_only && mode != INDEX &&
220 - rename(src, dst) < 0 && !ignore_errors)
221 - die_errno (_("renaming '%s' failed"), src);
227 + if (!show_only && mode != INDEX) {
228 + if (rename(src, dst) < 0 && !ignore_errors)
229 + die_errno (_("renaming '%s' failed"), src);
230 + if (submodule_gitfile[i])
231 + connect_work_tree_and_git_dir(dst, submodule_gitfile[i]);
232 + }
233
234 if (mode == WORKING_DIRECTORY)
235 continue;
submodule.c
+31
@@ -1004,3 +1004,34 @@ int merge_submodule(unsigned char result[20], const char *path,
1004 free(merges.objects);
1005 return 0;
1006 }
1007 +
1008 +/* Update gitfile and core.worktree setting to connect work tree and git dir */
1009 +void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
1010 +{
1011 + struct strbuf file_name = STRBUF_INIT;
1012 + struct strbuf rel_path = STRBUF_INIT;
1013 + const char *real_work_tree = xstrdup(real_path(work_tree));
1014 + FILE *fp;
1015 +
1016 + /* Update gitfile */
1017 + strbuf_addf(&file_name, "%s/.git", work_tree);
1018 + fp = fopen(file_name.buf, "w");
1019 + if (!fp)
1020 + die(_("Could not create git link %s"), file_name.buf);
1021 + fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
1022 + &rel_path));
1023 + fclose(fp);
1024 +
1025 + /* Update core.worktree setting */
1026 + strbuf_reset(&file_name);
1027 + strbuf_addf(&file_name, "%s/config", git_dir);
1028 + if (git_config_set_in_file(file_name.buf, "core.worktree",
1029 + relative_path(real_work_tree, git_dir,
1030 + &rel_path)))
1031 + die(_("Could not set core.worktree in %s"),
1032 + file_name.buf);
1033 +
1034 + strbuf_release(&file_name);
1035 + strbuf_release(&rel_path);
1036 + free((void *)real_work_tree);
1037 +}
submodule.h
+1
@@ -36,5 +36,6 @@ int merge_submodule(unsigned char result[20], const char *path, const unsigned c
36 int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
37 struct string_list *needs_pushing);
38 int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
39 +void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
40
41 #endif
t/t7001-mv.sh
+19
@@ -293,4 +293,23 @@ test_expect_success 'git mv moves a submodule with a .git directory and no .gitm
293 git diff-files --quiet
294 '
295
296 +test_expect_success 'git mv moves a submodule with gitfile' '
297 + rm -rf mod/sub &&
298 + git reset --hard &&
299 + git submodule update &&
300 + entry="$(git ls-files --stage sub | cut -f 1)" &&
301 + (
302 + cd mod &&
303 + git mv ../sub/ .
304 + ) &&
305 + ! test -e sub &&
306 + [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
307 + (
308 + cd mod/sub &&
309 + git status
310 + ) &&
311 + git update-index --refresh &&
312 + git diff-files --quiet
313 +'
314 +
315 test_done