* [PATCH] diff-tree -c: show a merge commit a bit more sensibly.
From: Junio C Hamano @ 2006-01-24 9:34 UTC (permalink / raw)
To: git; +Cc: torvalds
A new option '-c' to diff-tree changes the way a merge commit is
displayed when generating a patch output. It shows a "combined
diff" (hence the option letter 'c'), which looks like this:
$ git-diff-tree --pretty -c -p fec9ebf1 | head -n 18
diff-tree fec9ebf... (from parents)
Merge: 0620db3... 8a263ae...
Author: Junio C Hamano <junkio@cox.net>
Date: Sun Jan 15 22:25:35 2006 -0800
Merge fixes up to GIT 1.1.3
diff --combined describe.c
@@ +99,18 @@
}
- static void describe(char *arg)
- static void describe(struct commit *cmit, int last_one)
++ static void describe(char *arg, int last_one)
{
+ unsigned char sha1[20];
+ struct commit *cmit;
struct commit_list *list;
Unlike "gitk", this is monochrome output. A '-' character in
the nth column means the line is from the nth parent and does
not appear in the merge result. A '+' character in the nth
column means the line appears in the merge result, and the nth
parent does not have that line. The above example shows that the
function signature was changed from either parents (hence two
'-' lines and a '++' line), and "unsigned char sha1[20]",
prefixed by a " +", was inherited from the first parent.
The code probably is still buggy with rough edges, but with my
limited tests, it did not seem to dump core ;-).
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
Makefile | 2
combine-diff.c | 436 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff-tree.c | 34 ++++
diff.h | 2
4 files changed, 467 insertions(+), 7 deletions(-)
create mode 100644 combine-diff.c
b80f9de155bfc183839551e19e767859d689b593
diff --git a/Makefile b/Makefile
index 3046056..552b20f 100644
--- a/Makefile
+++ b/Makefile
@@ -182,7 +182,7 @@ LIB_H = \
DIFF_OBJS = \
diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
- diffcore-pickaxe.o diffcore-rename.o tree-diff.o
+ diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o
LIB_OBJS = \
blob.o commit.o connect.o count-delta.o csum-file.o \
diff --git a/combine-diff.c b/combine-diff.c
new file mode 100644
index 0000000..b47eda5
--- /dev/null
+++ b/combine-diff.c
@@ -0,0 +1,436 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "quote.h"
+
+struct path_list {
+ struct path_list *next;
+ int len;
+ char *path;
+ unsigned char sha1[20];
+ unsigned char parent_sha1[FLEX_ARRAY][20];
+};
+
+static int uninteresting(struct diff_filepair *p)
+{
+ if (diff_unmodified_pair(p))
+ return 1;
+ if (!S_ISREG(p->one->mode) || !S_ISREG(p->two->mode))
+ return 1;
+ return 0;
+}
+
+static struct path_list *intersect_paths(struct path_list *curr,
+ int n, int num_parent)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct path_list *p;
+ int i;
+
+ if (!curr) {
+ struct path_list *list = NULL, *tail = NULL;
+ for (i = 0; i < q->nr; i++) {
+ int len;
+ const char *path;
+ if (uninteresting(q->queue[i]))
+ continue;
+ path = q->queue[i]->two->path;
+ len = strlen(path);
+
+ p = xmalloc(sizeof(*p) + len + 1 + num_parent * 20);
+ p->path = (char*) &(p->parent_sha1[num_parent][0]);
+ memcpy(p->path, path, len);
+ p->path[len] = 0;
+ p->len = len;
+ p->next = NULL;
+ memcpy(p->sha1, q->queue[i]->two->sha1, 20);
+ memcpy(p->parent_sha1[n], q->queue[i]->one->sha1, 20);
+ if (!tail)
+ list = tail = p;
+ else {
+ tail->next = p;
+ p = tail;
+ }
+ }
+ return list;
+ }
+
+ for (p = curr; p; p = p->next) {
+ int found = 0;
+ if (!p->len)
+ continue;
+ for (i = 0; i < q->nr; i++) {
+ const char *path;
+ int len;
+
+ if (uninteresting(q->queue[i]))
+ continue;
+ path = q->queue[i]->two->path;
+ len = strlen(path);
+ if (len == p->len && !memcmp(path, p->path, len)) {
+ found = 1;
+ memcpy(p->parent_sha1[n],
+ q->queue[i]->one->sha1, 20);
+ break;
+ }
+ }
+ if (!found)
+ p->len = 0;
+ }
+ return curr;
+}
+
+struct lline {
+ struct lline *next;
+ int nth;
+ char line[FLEX_ARRAY];
+};
+
+struct sline {
+ struct lline *lost_head, **lost_tail;
+ char *bol;
+ int len;
+ unsigned long flag;
+};
+
+static char *grab_blob(const unsigned char *sha1, unsigned long *size)
+{
+ char *blob;
+ char type[20];
+ if (!memcmp(sha1, null_sha1, 20)) {
+ /* deleted blob */
+ *size = 0;
+ return xcalloc(1, 1);
+ }
+ blob = read_sha1_file(sha1, type, size);
+ if (strcmp(type, "blob"))
+ die("object '%s' is not a blob!", sha1_to_hex(sha1));
+ return blob;
+}
+
+#define TMPPATHLEN 50
+#define MAXLINELEN 10240
+
+static void write_to_temp_file(char *tmpfile, void *blob, unsigned long size)
+{
+ int fd = git_mkstemp(tmpfile, TMPPATHLEN, ".diff_XXXXXX");
+ if (fd < 0)
+ die("unable to create temp-file");
+ if (write(fd, blob, size) != size)
+ die("unable to write temp-file");
+ close(fd);
+}
+
+static void write_temp_blob(char *tmpfile, const unsigned char *sha1)
+{
+ unsigned long size;
+ void *blob;
+ blob = grab_blob(sha1, &size);
+ write_to_temp_file(tmpfile, blob, size);
+ free(blob);
+}
+
+static int parse_num(char **cp_p, unsigned int *num_p)
+{
+ char *cp = *cp_p;
+ unsigned int num = 0;
+ int read_some;
+
+ while ('0' <= *cp && *cp <= '9')
+ num = num * 10 + *cp++ - '0';
+ if (!(read_some = cp - *cp_p))
+ return -1;
+ *cp_p = cp;
+ *num_p = num;
+ return 0;
+}
+
+static int parse_hunk_header(char *line, int len,
+ unsigned int *ob, unsigned int *on,
+ unsigned int *nb, unsigned int *nn)
+{
+ char *cp;
+ cp = line + 4;
+ if (parse_num(&cp, ob)) {
+ bad_line:
+ return error("malformed diff output: %s", line);
+ }
+ if (*cp == ',') {
+ cp++;
+ if (parse_num(&cp, on))
+ goto bad_line;
+ }
+ else
+ *on = 1;
+ if (*cp++ != ' ' || *cp++ != '+')
+ goto bad_line;
+ if (parse_num(&cp, nb))
+ goto bad_line;
+ if (*cp == ',') {
+ cp++;
+ if (parse_num(&cp, nn))
+ goto bad_line;
+ }
+ else
+ *nn = 1;
+ return -!!memcmp(cp, " @@", 3);
+}
+
+static void append_lost(struct sline *sline, int n, const char *line)
+{
+ struct lline *lline;
+ int len = strlen(line);
+ if (line[len-1] == '\n')
+ len--;
+ lline = xmalloc(sizeof(*lline) + len + 1);
+ lline->next = NULL;
+ lline->nth = n;
+ if (sline->lost_head)
+ *(sline->lost_tail) = lline;
+ else
+ sline->lost_head = lline;
+ sline->lost_tail = &lline->next;
+ memcpy(lline->line, line, len);
+ lline->line[len] = 0;
+}
+
+static void combine_diff(const unsigned char *parent, const char *ourtmp,
+ struct sline *sline, int cnt, int n)
+{
+ FILE *in;
+ char parent_tmp[TMPPATHLEN];
+ char cmd[TMPPATHLEN * 2 + 1024];
+ char line[MAXLINELEN];
+ unsigned int lno, ob, on, nb, nn;
+ unsigned long pmask = ~(1UL << n);
+ struct sline *lost_bucket = NULL;
+
+ write_temp_blob(parent_tmp, parent);
+ sprintf(cmd, "diff --unified=0 -La/x -Lb/x '%s' '%s'",
+ parent_tmp, ourtmp);
+ in = popen(cmd, "r");
+ if (!in)
+ return;
+
+ lno = 1;
+ while (fgets(line, sizeof(line), in) != NULL) {
+ int len = strlen(line);
+ if (5 < len && !memcmp("@@ -", line, 4)) {
+ if (parse_hunk_header(line, len,
+ &ob, &on, &nb, &nn))
+ break;
+ lno = nb;
+ lost_bucket = &sline[nb-1]; /* sline is 0 based */
+ continue;
+ }
+ if (!lost_bucket)
+ continue;
+ switch (line[0]) {
+ case '-':
+ append_lost(lost_bucket, n, line+1);
+ break;
+ case '+':
+ sline[lno-1].flag &= pmask;
+ lno++;
+ break;
+ }
+ }
+ fclose(in);
+ unlink(parent_tmp);
+}
+
+static unsigned long context = 2;
+
+static int interesting(struct sline *sline, unsigned long all_mask)
+{
+ return ((sline->flag & all_mask) != all_mask || sline->lost_head);
+}
+
+static void make_hunks(struct sline *sline, unsigned long cnt, int num_parent)
+{
+ unsigned long all_mask = (1UL<<num_parent) - 1;
+ unsigned long mark = (1UL<<num_parent);
+ unsigned long i;
+
+ i = 0;
+ while (i < cnt) {
+ if (interesting(&sline[i], all_mask)) {
+ unsigned long j = (context < i) ? i - context : 0;
+ while (j <= i)
+ sline[j++].flag |= mark;
+ while (++i < cnt) {
+ if (!interesting(&sline[i], all_mask))
+ break;
+ sline[i].flag |= mark;
+ }
+ j = (i + context < cnt) ? i + context : cnt;
+ while (i < j)
+ sline[i++].flag |= mark;
+ continue;
+ }
+ i++;
+ }
+}
+
+static void dump_sline(struct sline *sline, int cnt, int num_parent)
+{
+ unsigned long mark = (1UL<<num_parent);
+ int i;
+ int lno = 0;
+
+ while (1) {
+ struct sline *sl = &sline[lno];
+ int hunk_end;
+ while (lno < cnt && !(sline[lno].flag & mark))
+ lno++;
+ if (cnt <= lno)
+ break;
+ for (hunk_end = lno + 1; hunk_end < cnt; hunk_end++)
+ if (!(sline[hunk_end].flag & mark))
+ break;
+ for (i = 0; i < num_parent; i++) putchar('@');
+ printf(" +%d,%d ", lno+1, hunk_end-lno);
+ for (i = 0; i < num_parent; i++) putchar('@');
+ putchar('\n');
+ while (lno < hunk_end) {
+ struct lline *ll;
+ sl = &sline[lno++];
+ ll = sl->lost_head;
+ int j;
+ while (ll) {
+ for (j = 0; j < ll->nth; j++)
+ putchar(' ');
+ putchar('-');
+ for (j = ll->nth + 1; j < num_parent; j++)
+ putchar(' ');
+ putchar(' ');
+ puts(ll->line);
+ ll = ll->next;
+ }
+ for (j = 0; j < num_parent; j++) {
+ if ((1UL<<j) & sl->flag)
+ putchar(' ');
+ else
+ putchar('+');
+ }
+ printf(" %.*s\n", sl->len, sl->bol);
+ }
+ }
+}
+
+static void show_combined_diff(struct path_list *elem, int num_parent)
+{
+ unsigned long size, cnt, lno;
+ char *result, *cp, *ep;
+ struct sline *sline; /* survived lines */
+ int i;
+ char ourtmp[TMPPATHLEN];
+
+ /* Read the result of merge first */
+ result = grab_blob(elem->sha1, &size);
+ write_to_temp_file(ourtmp, result, size);
+
+ for (cnt = 0, cp = result; cp - result < size; cp++) {
+ if (*cp == '\n')
+ cnt++;
+ }
+ if (result[size-1] != '\n')
+ cnt++; /* incomplete line */
+
+ sline = xcalloc(cnt, sizeof(*sline));
+ ep = result;
+ sline[0].bol = result;
+ for (lno = 0, cp = result; cp - result < size; cp++) {
+ if (*cp == '\n') {
+ sline[lno].len = cp - sline[lno].bol;
+ sline[lno].flag = (1UL<<num_parent) - 1;
+ lno++;
+ if (lno < cnt)
+ sline[lno].bol = cp + 1;
+ }
+ }
+ if (result[size-1] != '\n') {
+ sline[cnt-1].len = size - (sline[cnt-1].bol - result);
+ sline[cnt-1].flag = (1UL<<num_parent) - 1;
+ }
+
+ for (i = 0; i < num_parent; i++)
+ combine_diff(elem->parent_sha1[i], ourtmp, sline, cnt, i);
+
+ make_hunks(sline, cnt, num_parent);
+
+ dump_sline(sline, cnt, num_parent);
+ unlink(ourtmp);
+ free(result);
+
+ for (i = 0; i < cnt; i++) {
+ if (sline[i].lost_head) {
+ struct lline *ll = sline[i].lost_head;
+ while (ll) {
+ struct lline *tmp = ll;
+ ll = ll->next;
+ free(tmp);
+ }
+ }
+ }
+ free(sline);
+}
+
+int diff_tree_combined_merge(const unsigned char *sha1, const char *header)
+{
+ struct commit *commit = lookup_commit(sha1);
+ struct diff_options diffopts;
+ struct commit_list *parents;
+ struct path_list *p, *paths = NULL;
+ int num_parent, i, num_paths;
+
+ diff_setup(&diffopts);
+ diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diffopts.recursive = 1;
+
+ /* count parents */
+ for (parents = commit->parents, num_parent = 0;
+ parents;
+ parents = parents->next, num_parent++)
+ ; /* nothing */
+
+ /* find set of paths that everybody touches */
+ for (parents = commit->parents, i = 0;
+ parents;
+ parents = parents->next, i++) {
+ struct commit *parent = parents->item;
+ diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
+ &diffopts);
+ paths = intersect_paths(paths, i, num_parent);
+ diff_flush(&diffopts);
+ }
+
+ /* find out surviving paths */
+ for (num_paths = 0, p = paths; p; p = p->next) {
+ if (p->len)
+ num_paths++;
+ }
+ if (num_paths) {
+ puts(header);
+ for (p = paths; p; p = p->next) {
+ if (!p->len)
+ continue;
+ printf("diff --combined ");
+ if (quote_c_style(p->path, NULL, NULL, 0))
+ quote_c_style(p->path, NULL, stdout, 0);
+ else
+ printf("%s", p->path);
+ putchar('\n');
+ show_combined_diff(p, num_parent);
+ }
+ }
+
+ /* Clean things up */
+ while (paths) {
+ struct path_list *tmp = paths;
+ paths = paths->next;
+ free(tmp);
+ }
+ return 0;
+}
diff --git a/diff-tree.c b/diff-tree.c
index efa2b94..79f2497 100644
--- a/diff-tree.c
+++ b/diff-tree.c
@@ -6,6 +6,7 @@ static int show_root_diff = 0;
static int no_commit_id = 0;
static int verbose_header = 0;
static int ignore_merges = 1;
+static int combine_merges = 0;
static int read_stdin = 0;
static const char *header = NULL;
@@ -79,9 +80,13 @@ static const char *generate_header(const
offset = sprintf(this_header, "%s%s ",
header_prefix,
diff_unique_abbrev(commit_sha1, abbrev));
- offset += sprintf(this_header + offset, "(from %s)\n",
- parent_sha1 ?
- diff_unique_abbrev(parent_sha1, abbrev) : "root");
+ if (commit_sha1 != parent_sha1)
+ offset += sprintf(this_header + offset, "(from %s)\n",
+ parent_sha1
+ ? diff_unique_abbrev(parent_sha1, abbrev)
+ : "root");
+ else
+ offset += sprintf(this_header + offset, "(from parents)\n");
offset += pretty_print_commit(commit_format, msg, len,
this_header + offset,
sizeof(this_header) - offset);
@@ -108,8 +113,15 @@ static int diff_tree_commit(const unsign
}
/* More than one parent? */
- if (ignore_merges && commit->parents && commit->parents->next)
- return 0;
+ if (commit->parents && commit->parents->next) {
+ if (ignore_merges)
+ return 0;
+ else if (combine_merges) {
+ header = generate_header(sha1, sha1,
+ commit->buffer);
+ return diff_tree_combined_merge(sha1, header);
+ }
+ }
for (parents = commit->parents; parents; parents = parents->next) {
struct commit *parent = parents->item;
@@ -154,7 +166,7 @@ static int diff_tree_stdin(char *line)
}
static const char diff_tree_usage[] =
-"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"git-diff-tree [--stdin] [-m] [-c] [-s] [-v] [--pretty] [-t] [-r] [--root] "
"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
" -r diff recursively\n"
" --root include the initial commit as diff against /dev/null\n"
@@ -217,6 +229,11 @@ int main(int argc, const char **argv)
ignore_merges = 0;
continue;
}
+ if (!strcmp(arg, "-c")) {
+ ignore_merges = 0;
+ combine_merges = 1;
+ continue;
+ }
if (!strcmp(arg, "-v")) {
verbose_header = 1;
header_prefix = "diff-tree ";
@@ -245,6 +262,11 @@ int main(int argc, const char **argv)
if (diff_options.output_format == DIFF_FORMAT_PATCH)
diff_options.recursive = 1;
+ if (combine_merges) {
+ if (diff_options.output_format != DIFF_FORMAT_PATCH)
+ die("-c cannot be used without -p");
+ }
+
diff_tree_setup_paths(get_pathspec(prefix, argv));
diff_setup_done(&diff_options);
diff --git a/diff.h b/diff.h
index 5696f2a..c0e8e0e 100644
--- a/diff.h
+++ b/diff.h
@@ -56,6 +56,8 @@ extern int diff_tree(struct tree_desc *t
extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
const char *base, struct diff_options *opt);
+extern int diff_tree_combined_merge(const unsigned char *sha1, const char *);
+
extern void diff_addremove(struct diff_options *,
int addremove,
unsigned mode,
--
1.1.4.g2bb7f
^ permalink raw reply related
* Re: [PATCH] rev-{list,parse}: allow -<n> as shorthand for --max-count=<n>
From: Junio C Hamano @ 2006-01-24 8:02 UTC (permalink / raw)
To: Eric Wong; +Cc: git list
In-Reply-To: <20060124072946.GA9468@Muzzle>
Eric Wong <normalperson@yhbt.net> writes:
> Some versions of head(1) and tail(1) allow their line limits to be
> parsed this way. I find --max-count to be a commonly used option,
> and also similar in spirit to head/tail, so I decided to make life
> easier on my worn out (and lazy :) fingers with this patch.
As an old timer, I personally am very used to "head -4", but
also have been training my fingers to say "head -n 4" for the
past few years, because the former is not POSIXly correct.
At the same time, I agree that --max-count *was* a mistake. We
should maybe say "-n <n>" perhaps?
^ permalink raw reply
* [PATCH] rev-{list,parse}: allow -<n> as shorthand for --max-count=<n>
From: Eric Wong @ 2006-01-24 7:29 UTC (permalink / raw)
To: git list
Some versions of head(1) and tail(1) allow their line limits to be
parsed this way. I find --max-count to be a commonly used option,
and also similar in spirit to head/tail, so I decided to make life
easier on my worn out (and lazy :) fingers with this patch.
Signed-off-by: Eric Wong <normalperson@yhbt.net>
---
rev-list.c | 5 +++++
rev-parse.c | 4 ++++
2 files changed, 9 insertions(+), 0 deletions(-)
68df4b28986a4642119373e18a63751be0e26366
diff --git a/rev-list.c b/rev-list.c
index d060966..2bbd699 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -732,6 +732,11 @@ int main(int argc, const char **argv)
struct commit *commit;
unsigned char sha1[20];
+ /* accept, -<digit>, like some versions of head/tail */
+ if (*arg == '-' && isdigit(arg[1])) {
+ max_count = atoi(arg + 1);
+ continue;
+ }
if (!strncmp(arg, "--max-count=", 12)) {
max_count = atoi(arg + 12);
continue;
diff --git a/rev-parse.c b/rev-parse.c
index 0c951af..4dfc1a9 100644
--- a/rev-parse.c
+++ b/rev-parse.c
@@ -52,6 +52,10 @@ static int is_rev_argument(const char *a
};
const char **p = rev_args;
+ /* accept, -<digit>, like some versions of head/tail */
+ if (*arg == '-' && isdigit(arg[1]))
+ return 1;
+
for (;;) {
const char *str = *p++;
int len;
--
1.1.4.g68df
^ permalink raw reply related
* Re: StGIT: "stg new" vs "stg new --force"
From: Pavel Roskin @ 2006-01-24 5:30 UTC (permalink / raw)
To: J. Bruce Fields; +Cc: Yann Dirson, Catalin Marinas, git, Charles Lever
In-Reply-To: <20060120182255.GD32177@fieldses.org>
On Fri, 2006-01-20 at 13:22 -0500, J. Bruce Fields wrote:
> I tend to use stg refresh -es as a quick (well, not quite as quick as
> I'd like) way to look at the current patch. Often I leave it up while
> I'm working (editing the patched files). So if exiting from stg refresh
> -es suddenly started overwriting my working files, I'd be very
> unhappy....
If I understand correctly, "stg refresh" only modifies the repository,
not the files in the local directory. This shouldn't change.
It's only having two instances of "stg refresh -es" that would be a
problem if conflicting edits are made in both editors. The answer to
that is "don't do it".
--
Regards,
Pavel Roskin
^ permalink raw reply
* Re: [RFC] Reverting "git push logic change"?
From: Greg KH @ 2006-01-24 5:05 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vfynivx9s.fsf_-_@assigned-by-dhcp.cox.net>
On Fri, Jan 20, 2006 at 11:46:23PM -0800, Junio C Hamano wrote:
> The change introduced by 9e9b267 commit broke "correct" usage of
> git push to push matching refs, to work around a problem
> observed in a usage pattern on a shared repository.
<snip>
Thanks, I think this makes a bit more sense.
If nothing else, the documentation should be updated to reflect the
change in 9e9b267 :)
thanks,
greg k-h
^ permalink raw reply
* Re: Notes on Subproject Support
From: Daniel Barkalow @ 2006-01-24 4:22 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vek2yxukm.fsf@assigned-by-dhcp.cox.net>
On Mon, 23 Jan 2006, Junio C Hamano wrote:
> Daniel Barkalow <barkalow@iabervon.org> writes:
>
> > I think it would be a lot more fragile if switching branches requires
> > multiple programs interacting with the index file. If things get
> > interrupted after the tree is read but before the bindings are changed,
> > the user will probably generate an inconsistant commit or have to deal
> > with figuring out what's going on. It is a nice property of the current
> > system that the index file never exists under the usual filename without
> > being consistant.
>
> That is certainly an issue, which we have had already for quite
> some time, I am afraid. We can get interrupted during "switch
> branches" flow after read-tree -u -m but before updating HEAD.
> We can also get interrupted during "commit" flow after writing
> the commit object out before updating the ref pointed at by
> HEAD. No?
The switch branches one is accurate, but I think that, if we get
interrupted before updating the ref, the index will still be the same, and
we'll just have a dangling object (which, if we commit the same thing
again, will be the same object we generate).
I suppose the existing branch switching isn't much less bad than the new
one would be, though. I sort of worry that rewriting the index file is
more likely to be interrupted than updating a ref, but that's probably not
really a significant difference.
> If we are truly serious about solving the issue of getting
> interrupted in the middle, I suspect we have to take the "index
> is a staging area for the next commit" approach I digressed into
> last night. It would involve introducing a git-atomic-checkout
> command to replace the current "git-rev-parse, git-read-tree,
> then git-symbolic-ref" sequence in the checkout flow.
Well, if the value of HEAD were in the index file, that would be
sufficient to prevent anything actually bad from happening in the checkout
path; if it gets interrupted, the index file's "current commit" field
would then not match the ref and it would be clear that the system was in
an intermediate state. (It would appear like if you'd fetched into the
current branch without it doing the fast-forward.)
> In the commit flow, we would need git-commit-index command to replace
> the current "git-write-tree, git-commit-tree, then
> git-update-ref" sequence.
I don't think there's an issue here, anyway.
> I am not particularly opposed to that, but I suspect it might be
> a moderate amount of work for very little gain. Continuing with
> the digression, the updated index file may contain:
>
> 1. list of <blob path, object name>
> 2. list of parent commit object names for the next commit
> 3. the name of the local branch to create the next commit on
> 4. for each bound path:
> list of parent commit object names for that path.
>
> 1. is what we have in the current (version 2) index file.
>
> 2. contains:
> - 0 commit in an index file before the initial commit
> - 1 commit in an index file after a fresh checkout and records
> the commit object name we checked out (replaces HEAD+heads/$branch)
> - 2 commits in an index file after 3-way read-tree, or more
> during an Octopus merge (replaces HEAD+MERGE_HEAD)
>
> 3. may not be needed, but if we did so, it would replace HEAD.
I don't think it would be needed; it could certainly be passed in.
Actually not having HEAD would complicate a lot of programs that use HEAD
but don't currently read the index (and don't actually care about whether
you have the branch that you consider current actually checked out).
> 4. is similar to 2 but for bound subprojects. Usually we have
> one commit per bound path to record the "bind" line commit we
> read from the commit object after a fresh checkout. During a
> subproject merge, we would:
> - start out with 1 commit read from the "bind" line;
> - merging in another subproject commit would add that commit;
> - when making a new subproject commit, the recorded commits
> are used as its parents.
Right.
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
* Caching directories
From: Pavel Roskin @ 2006-01-24 4:20 UTC (permalink / raw)
To: git
Hello!
I'm thinking of moving cg-clean functionality to git. After having
switched to StGIT, it's the last cogito command I'm still using. I
think git can go it much better, since it's a recursive command
traversing the whole repository.
To be safe and useful, the new command should distinguish between
tracked and untracked directories. Untracked files in tracked
directories are usually the first target for cleaning, as they are
mostly automatic backups and temporary files. Untracked directories are
more likely candidates to be preserved, as they can hold external
sources, build output, extensive test data etc.
cg-clean considers a directory untracked if it has no cached files in
it. This carries a significant speed penalty, even if not coded in
bash.
Maybe it's time to start caching directories in git? I mean,
directories corresponding to tree objects could have their stats
recorded in the cache. This would allow to distinguish between tracked
and untracked directories without scanning them recursively.
--
Regards,
Pavel Roskin
^ permalink raw reply
* Re: What is in git.git
From: Josef Weidendorfer @ 2006-01-24 1:52 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v64odj821.fsf@assigned-by-dhcp.cox.net>
On Sunday 22 January 2006 03:44, you wrote:
> In commit 1:
> bind xxxxx... X/
Ah, OK, sorry.
This was a misunderstanding from my side; please forget the
other suggestion about change reference files, too ;-)
Josef
^ permalink raw reply
* Re: Notes on Subproject Support
From: Junio C Hamano @ 2006-01-24 1:50 UTC (permalink / raw)
To: Daniel Barkalow; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0601231200380.25300@iabervon.org>
Daniel Barkalow <barkalow@iabervon.org> writes:
> ... Do you see an advantage to having the index only record the
> information used for making a tree, and keeping the information for making
> a commit in other files?
If somebody else already did the work and presented me two git
implementations, one with the index file capable of generating a
tree and uses separate files to keep track of other information
for commits, and the other with the index file with everything
needed for a commit, I'd certainly take the latter. In that
sense, I do not see such an advantage at all. The practical
advantage of keeping them separate is to keep things simple,
minimizing the changes. I see the subproject support as a
secondary issue, and so far I haven't found a reason convincing
enough to tell me that it is better to put HEAD+heads/$branch
information in the index itself when used in a subproject-less
setup. It perhaps would make us more robust when interrupted in
the middle of switching branches or making a commit, but that is
about it (I do not particularly see that a serious problem).
>> *1* One good property of "gitlink" approach is that we *could*
>> extend this blob-like object to store arbitrary human readable
>> information to represent a point-in-time from an arbitrary
>> foreign SCM system. IOW, we do not necessarily have to require
>> `commit` line that name a git commit to be there. It could say
>> "Please slurp http://www.kernel.org/pub/software/.../git.tar.gz
>> and extract it in git/ directory".
>> ...
> I don't think this would really be useful. The reason to have the included
> revision stored in a way that's explicitly marked for git to find is that
> git can do useful things with the information ...
> but more importantly, making sure that changes to what revision
> you're working with propagate to changes in what revision you specify
> should be there)...
My example was taking things to the extreme to be illustrative.
To be more practical, it could have pointed at "git-1.0.tar.gz"
or an "svn://" URL with explicit revision name, which ought to
be enough to recreate the exact state.
^ permalink raw reply
* Re: Notes on Subproject Support
From: Junio C Hamano @ 2006-01-24 1:50 UTC (permalink / raw)
To: Daniel Barkalow; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0601231116550.25300@iabervon.org>
Daniel Barkalow <barkalow@iabervon.org> writes:
> I think it would be a lot more fragile if switching branches requires
> multiple programs interacting with the index file. If things get
> interrupted after the tree is read but before the bindings are changed,
> the user will probably generate an inconsistant commit or have to deal
> with figuring out what's going on. It is a nice property of the current
> system that the index file never exists under the usual filename without
> being consistant.
That is certainly an issue, which we have had already for quite
some time, I am afraid. We can get interrupted during "switch
branches" flow after read-tree -u -m but before updating HEAD.
We can also get interrupted during "commit" flow after writing
the commit object out before updating the ref pointed at by
HEAD. No?
If we are truly serious about solving the issue of getting
interrupted in the middle, I suspect we have to take the "index
is a staging area for the next commit" approach I digressed into
last night. It would involve introducing a git-atomic-checkout
command to replace the current "git-rev-parse, git-read-tree,
then git-symbolic-ref" sequence in the checkout flow. In the
commit flow, we would need git-commit-index command to replace
the current "git-write-tree, git-commit-tree, then
git-update-ref" sequence.
I am not particularly opposed to that, but I suspect it might be
a moderate amount of work for very little gain. Continuing with
the digression, the updated index file may contain:
1. list of <blob path, object name>
2. list of parent commit object names for the next commit
3. the name of the local branch to create the next commit on
4. for each bound path:
list of parent commit object names for that path.
1. is what we have in the current (version 2) index file.
2. contains:
- 0 commit in an index file before the initial commit
- 1 commit in an index file after a fresh checkout and records
the commit object name we checked out (replaces HEAD+heads/$branch)
- 2 commits in an index file after 3-way read-tree, or more
during an Octopus merge (replaces HEAD+MERGE_HEAD)
3. may not be needed, but if we did so, it would replace HEAD.
4. is similar to 2 but for bound subprojects. Usually we have
one commit per bound path to record the "bind" line commit we
read from the commit object after a fresh checkout. During a
subproject merge, we would:
- start out with 1 commit read from the "bind" line;
- merging in another subproject commit would add that commit;
- when making a new subproject commit, the recorded commits
are used as its parents.
^ permalink raw reply
* Re: Notes on Subproject Support
From: Junio C Hamano @ 2006-01-23 19:30 UTC (permalink / raw)
To: Martin Atukunda; +Cc: git
In-Reply-To: <20060123125013.GA4472@igloo.ds.co.ug>
Martin Atukunda <matlads@dsmagic.com> writes:
> This proposal doesn't seem to cator for the event when a directory is
> renamed or moved to a different location, or am I missing something?
First of all, please do not top post.
Second of all, please do not quote the whole thing.
Third of all, if you quote, please read the parts you quote.
>> Merging
>> -------
>> ...
>> Such renaming can be handled by first moving the bind points in
>> our branch, and redoing the merge (this is a rare operation
>> anyway). It might go like this:
>> ...
This step describes how bind-point might be relocated prior to a
merge.
^ permalink raw reply
* Re: Notes on Subproject Support
From: Daniel Barkalow @ 2006-01-23 17:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Petr Baudis
In-Reply-To: <7v64ob1omh.fsf@assigned-by-dhcp.cox.net>
On Sun, 22 Jan 2006, Junio C Hamano wrote:
> Junio C Hamano <junkio@cox.net> writes:
>
> BTW, let's digress a bit.
>
> I think recording "commit" in the tree objects is in line with
> the logical organization described in README: "blob" and "tree"
> represent a state, and have *nothing* to do with how we came
> about to that state. The historyh is described in "commit"
> objects. The bound commit approach keeps that property.
>
> The "gitlink" approach, as I understand how Linus outlined in
> his original suggestion, is a bit different. The link objects
> appear in tree objects, and when you "git cat-file link" one of
> them, you would see something like this:
>
> commit 5b2bcc7b2d546c636f79490655b3347acc91d17f
> name kernel
>
> So in that sense, "gitlink" approach departs from the original
> premise of "commit" being the only thing that ties things
> together. Tree objects with "gitlink" know where they are in
> the history [*1*].
>
> By this, I do not mean to say that "gitlink" approach is
> inferior because it breaks that original premise. I am just
> pointing it out as a difference between two approaches.
I think it's hard to say whether the history of a subproject is part of
the state of the superproject or part of its history. They're certainly
not the same history, because the superproject history may record that
the superproject switched from one fork of a subproject to a different
fork, or reverted the subproject to an earlier version, or other such
things. (It's the whole data/metadata issue: when you take a step back,
one level's data and metadata are all data, and there's more stuff that's
metadata.)
I'd say that with either commits in trees or gitlink objects, it's still
only the commits that tie things together; but some of the things that
they tie together are opaquely things tied to other things. Tree objects
with gitlink/commits don't know where *they* are in the history; they
just happen to store things which know about a different history.
> Now, the current way index file is used is as a staging area to
> create a new commit on top of the tip of the current branch.
> However, it is interesting to note that logically, by itself
> *alone*, it cannot be used that way. The information the index
> file records is something that can be used to write out a tree
> object, and not a commit, because it does not know where the
> current state sits in the history. We have two separate files,
> $GIT_DIR/HEAD that records which branch we are on, and the
> branch head ref the HEAD points at, which records where the
> current index came from, for that purpose. The latter tells us
> what commit we should use as the parent commit if we create such
> a commit, and the former tells us which branch head to update
> once we create one. So in that sense, the index file is just a
> staging area to create a new tree, not a new commit.
Of course, we don't strictly need $GIT_DIR/HEAD to create a commit;
that's only needed for what we generally do with the commit once we have
it. We do need the branch head ref (or, more abstractly, the commit that
we read to generate the index we modified) in order to create the commit.
> We could have done things differently. I am not advocating to
> do the following change, but offering a possibility as a thought
> experiment. It just felt interesting enough to point them out.
>
> The index file could have recorded what commit the current state
> recorded in the index came from. By recording the commit the
> index was read from in the index itself, independently from the
> $GIT_DIR/refs/heads/$branch file, we could have been able to
> allow fetching into the current branch. When the $branch file
> for the current branch was updated by a fast-forward fetch, we
> would notice that the commit recorded there no longer match what
> is recorded in the index.
I actually think this would have been a good idea. I think we've had
approximately every possible bug that could come from inconsistancy
between the files that give the parents and the index file. (I think Linus
didn't do it that way initially just because he was thinking of it as a
cache, and there's little point in caching something small, and by the
time we started looking at it as primary information on its own, we'd
stopped thinking about what should go in it.)
> Another interesting consequence is if the development is a
> single repository and linear, we did not even need any file in
> $GIT_DIR/refs/ ("branchless git"). The commit recorded as the
> topmost in the index file itself would have served as the tip of
> the development, and we would have been able to tangle the
> history starting from the commit in the index file.
Well, you wouldn't be able to check out an old version and then return to
the present without dredging the objects database for the dangling commit.
My memory has gotten fuzzy, but I think HEAD may have originally been just
a file, and we effectively had this (except that HEAD and the index were
not the same file as far as the filesystem was concerned).
> While we are doing a thought experiment, let's say we allow to
> record more than one commits the current index is based upon.
> 'git merge' would record all the parent commits there, so that
> writing out the merge result out of the index file as a tree and
> then recording these commits as parents would have been the way
> to create a merge commit. We would not need the auxiliary file
> $GIT_DIR/MERGE_HEAD if we did so.
>
> In other words, if the index file recorded the commits its
> contents were based upon, instead of being a staging area for a
> new tree, it would have been a staging area for a new commit.
>
> Now, the latest proposal, borrowing your idea, records the
> subproject commits bound to subdirectories in the index itself.
> This is halfway to make the index file a staging area for the
> next commit. If we were to do that, we also *could* record the
> commits the current index is based upon, so that it can truly be
> used as a staging area to create a new commit, not just a tree.
>
> On the other hand, this could be a reason *not* to do the
> `update-index --bind` to record the subproject information in
> the index file. An auxiliary file such as $GIT_DIR/bind might
> be sufficient, just like $GIT_DIR/MERGE_HEAD has been good
> enough for us so far. One difference between MERGE_HEAD and
> bind is that the former is very transient -- only exists during
> a merge while the latter is persistent while the top commit is
> checked out and being worked on.
We've been able to make MERGE_HEAD work, but I remember there being
problems even there when people tried to abandon merges by changing
branches. Do you see an advantage to having the index only record the
information used for making a tree, and keeping the information for making
a commit in other files?
> [Footnote]
>
> *1* One good property of "gitlink" approach is that we *could*
> extend this blob-like object to store arbitrary human readable
> information to represent a point-in-time from an arbitrary
> foreign SCM system. IOW, we do not necessarily have to require
> `commit` line that name a git commit to be there. It could say
> "Please slurp http://www.kernel.org/pub/software/.../git.tar.gz
> and extract it in git/ directory".
>
> Of course, for such a toplevel project commit, the tool may not
> be able to do a checkout automatically and require the user to
> cat-file the link, download a tarball and extract the subtree
> there manually.
>
> The bound commit approach requires you to have git commit object
> names on the `bind` lines, and it is fundamentally much harder
> to extend it to allow interfacing with foreign (non-)SCM
> systems.
I don't think this would really be useful. The reason to have the included
revision stored in a way that's explicitly marked for git to find is that
git can do useful things with the information (such as checking it out for
you, but more importantly, making sure that changes to what revision
you're working with propagate to changes in what revision you specify
should be there). If the bound project is foreign, this clearly isn't
going to happen, so there's not much point. For your example above, you
could just have a regular file, "git/README", with the content "Please
download http://.../git.tar.gz, and extract it here", and it would be at
least as good.
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
* Re: Notes on Subproject Support
From: Daniel Barkalow @ 2006-01-23 16:48 UTC (permalink / raw)
To: Alexander Litvinov; +Cc: Junio C Hamano, git, Petr Baudis
In-Reply-To: <200601231206.53466.lan@ac-sw.com>
On Mon, 23 Jan 2006, Alexander Litvinov wrote:
> In our development we use a little bit other case (it is simplified):
> 1. We have self written C++ library for linked list implementation. Lets call
> it liblist.
> 2. We have project A that use liblist as separate directory and project B that
> use this library too.
>
> Currently we have 3 cvs projects with cvs-modules for linking liblist to A and
> B. During development of A and B we often modify liblist to fix bugs and
> these changes are immidatly visible to all projects who use liblist.
>
> After full implementation of bind functionality I see one restriction: I have
> to use one repo for storing all three projects: A, B and liblist to make
> changes of liblist visible to all projects. The solution is to make separate
> repos and on each change of liblist in prokect A push these changes to
> liblist repo and pull them into project B again - bit hacky solution.
We haven't yet discussed how pushing a repository with subprojects would
work. We could probably have an extra line in the remotes/ file to make
the desired thing happen automatically, so that you can just do "git push"
and have the subproject go to its repository and the main project go to
its repository.
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
* Re: Notes on Subproject Support
From: Daniel Barkalow @ 2006-01-23 16:31 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v7j8r7e7s.fsf@assigned-by-dhcp.cox.net>
On Sun, 22 Jan 2006, Junio C Hamano wrote:
> Daniel Barkalow <barkalow@iabervon.org> writes:
>
> >> Switching branches
> >> ------------------
> >>
> >> Along with the traditional two-way merge by `read-tree -m -u`,
> >> we would need to look at:
> >>
> >> . `bind` lines in the current `HEAD` commit.
> >>
> >> . `bind` lines in the commit we are switching to.
> >>
> >> . subproject binding information in the index file.
> >>
> >> to make sure we do sensible things.
> >
> > This is one place I think storing the bindings in the commit is awkward.
> > read-tree deals in trees (hence the name), but will need information from
> > the commit.
>
> That's why it is 'along with'. Dealing with binding information
> can be done between commits and index without bothering tree
> objects. read-tree would not have to deal with it, and I think
> keeping it that way is probably a good idea.
I think it would be a lot more fragile if switching branches requires
multiple programs interacting with the index file. If things get
interrupted after the tree is read but before the bindings are changed,
the user will probably generate an inconsistant commit or have to deal
with figuring out what's going on. It is a nice property of the current
system that the index file never exists under the usual filename without
being consistant.
> In other words, I think the design so far does not require us to
> touch tree objects at all, and I'd be happy if we do not have to.
>
> One reason I started the bound commit approach was exactly
> because I only needed to muck with commit objects and did not
> have to touch trees and blobs; after trying to implement the
> core level for "gitlink", which I ended up touching quite a lot
> and have abandoned for now.
I think that the same thing that worked with the index file would work for
minimizing the impact of the changes as far as the code sees. If the
struct tree parser reported the tree at a path when it found a commit at a
path, it would work just as if the original tree had used trees (like in
your proposal).
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
* Re: [PATCH] DT_UNKNOWN: do not fully trust existence of DT_UNKNOWN
From: Alex Riesen @ 2006-01-23 13:07 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vpsmmt32x.fsf@assigned-by-dhcp.cox.net>
On 1/21/06, Junio C Hamano <junkio@cox.net> wrote:
> Alex Riesen <raa.lkml@gmail.com> writes:
>
> >> +#undef DT_UNKNOWN
> >> +#undef DT_DIR
> >> +#undef DT_REG
> >> +#undef DT_LNK
> >
> > yes, of course
>
> That is technically correct but I suspect it would not matter in
> practice. The only thing you are avoiding is four compiler
> warnings when compiling for Cygwin of this week (I heard this is
> already fixed in Cygwin CVS). On older Cygwin you simply would
> not use NO_D_TYPE_IN_DIRENT, since d_type worked before. No?
Yes :)
^ permalink raw reply
* Re: Notes on Subproject Support
From: Martin Atukunda @ 2006-01-23 12:50 UTC (permalink / raw)
To: git
In-Reply-To: <7v3bjfafql.fsf@assigned-by-dhcp.cox.net>
This proposal doesn't seem to cator for the event when a directory is
renamed or moved to a different location, or am I missing something?
- Martin -
On Sun, Jan 22, 2006 at 05:35:14PM -0800, Junio C Hamano wrote:
> This is still a draft/WIP, but "release early" is a good
> discipline, so...
>
> -- >8 --
>
> Notes on Subproject Support
> ===========================
> Junio C Hamano <junkio@cox.net>
> v1.0 January 22, 2006
>
> Scenario
> --------
>
> The examples in the following discussion show how this proposal
> plans to help this:
>
> . A project to build an embedded Linux appliance "gadget" is
> maintained with git.
>
> . The project uses linux-2.6 kernel as its subcomponent. It
> starts from a particular version of the mainline kernel, but
> adds its own code and build infrastructure to fit the
> appliance's needs.
>
> . The working tree of the project is laid out this way:
> +
> ------------
> Makefile - Builds the whole thing.
> linux-2.6/ - The kernel, perhaps modified for the project.
> appliance/ - Applications that run on the appliance, and
> other bits.
> ------------
>
> . The project is willing to maintain its own changes out of tree
> of the Linux kernel project, but would want to be able to feed
> the changes upstream, and incorporate upstream changes to its
> own tree, taking advantage of the fact that both itself and
> the Linux kernel project are version controlled with git.
>
> The idea here is to:
>
> . Keep `linux-2.6/` part as an independent project. The work by
> the project on the kernel part can be naturally exchanged with
> the other kernel developers this way. Specifically, a tree
> object contained in commit objects belonging to this project
> does *not* have linux-2.6/ directory at the top.
>
> . Keep the `appliance/` part as another independent project.
> Applications are supposed to be more or less independent from
> the kernel version, but some other bits might be tied to a
> specific kernel version. Again, a tree object contained in
> commit objects belonging to this project does *not* have
> appliance/ directory at the top.
>
> . Have another project that combines the whole thing together,
> so that the project can keep track of which versions of the
> parts are built together.
>
> We will call the project that binds things together the
> 'toplevel project'. Other projects that hold `linux-2.6/` part
> and `appliance/` part are called 'subprojects'.
>
> Notice that `Makefile` at the top is part of the toplevel
> project in this example, but it is not necessary. We could
> instead have the appliance subproject include this file. In
> such a setup, the appliance subproject would have had `Makefile`
> and `appliance/` directory at the toplevel.
>
>
> Setting up
> ----------
>
> Let's say we have been working on the appliance software,
> independently version controlled with git. Also the kernel part
> has been version controlled separately, like this:
> ------------
> $ ls -dF current/*/.git current/*
> current/Makefile current/appliance/.git/ current/linux-2.6/.git/
> current/appliance/ current/linux-2.6/
> ------------
>
> Now we would want to get a combined project. First we would
> clone from these repositories (which is not strictly needed --
> we could use `$GIT_ALTERNATE_OBJECT_DIRECTORIES` instead):
>
> ------------
> $ mkdir combined && cd combined
> $ cp ../current/Makefile .
> $ git init-db
> $ mkdir -p .git/refs/subs/{kernel,gadget}/{heads,tags}
> $ git clone-pack ../current/linux-2.6/ master | read kernel_commit junk
> $ git clone-pack ../current/appliance/ master | read gadget_commit junk
> ------------
>
> We will introduce a new command to set up a combined project:
>
> ------------
> $ git bind-projects \
> $kernel_commit linux-2.6/ \
> $gadget_commit appliance/
> ------------
>
> This would do an equivalent of:
>
> ------------
> $ git read-tree --prefix=linux-2.6/ $kernel_commit
> $ git read-tree --prefix=appliance/ $gadget_commit
> ------------
> [NOTE]
> ============
> Earlier outlines sent to the git mailing list talked
> about `$GIT_DIR/bind` to record what subproject are bound to
> which subtree in the curent working tree and index. This
> proposal instead records that information in the index file
> when `--prefix=linux-2.6/` is given to `read-tree`.
>
> Also note that in this round of proposal, there is no separate
> branches that keep track of heads of subprojects.
> ============
>
> Let's not forget to add the `Makefile`, and check the whole
> thing out from the index file.
> ------------
> $ git add Makefile
> $ git checkout-index -f -u -q -a
> ------------
>
> Now our directory should be identical with the `current`
> directory. After making sure of that, we should be able to
> commit the whole thing:
>
> ------------
> $ diff -x .git -r ../current ../combined
> $ git commit -m 'Initial toplevel project commit'
> ------------
>
> Which should create a new commit object that records what is in
> the index file as its tree, with `bind` lines to record which
> subproject commit objects are bound at what subdirectory, and
> updates the `$GIT_DIR/refs/heads/master`. Such a commit object
> might look like this:
> ------------
> tree 04803b09c300c8325258ccf2744115acc4c57067
> bind 5b2bcc7b2d546c636f79490655b3347acc91d17f linux-2.6/
> bind 0bdd79af62e8621359af08f0afca0ce977348ac7 appliance/
> author Junio C Hamano <junio@kernel.org> 1137965565 -0800
> committer Junio C Hamano <junio@kernel.org> 1137965565 -0800
>
> Initial toplevel project commit
> ------------
>
>
> Making further commits
> ----------------------
>
> The easiest case is when you updated the Makefile without
> changing anything in the subprojects. In such a case, we just
> need to create a new commmit object that records the new tree
> with the current `HEAD` as its parent, and with the same set of
> `bind` lines.
>
> When we have changes to the subproject part, we would make a
> separate commit to the subproject part and then record the whole
> thing by making a commit to the toplevel project. The user
> interaction might go this way:
> ------------
> $ git commit
> error: you have changes to the subproject bound at linux-2.6/.
> $ git commit --subproject linux-2.6/
> $ git commit
> ------------
>
> With the new `--subproject` option, the directory structure
> rooted at `linux-2.6/` part is written out as a tree, and a new
> commit object that records that tree object with the commit
> bound to that portion of the tree (`5b2bcc7b` in the above
> example) as its parent is created. Then the final `git commit`
> would record the whole tree with updated `bind` line for the
> `linux-2.6/` part.
>
>
> Checking out
> ------------
>
> After cloning such a toplevel project, `git clone` without `-n`
> option would check out the working tree. This is done by
> reading the tree object recorded in the commit object (which
> records the whole thing), and adding the information from the
> "bind" line to the index file.
>
> ------------
> $ cd ..
> $ git clone -n combined cloned ;# clone the one we created earlier
> $ cd cloned
> $ git checkout
> ------------
>
> This round of proposal does not maintain separate branch heads
> for subprojects. The bound commits and their subdirectories
> are recorded in the index file from the commit object, so there
> is no need to do anything other than updating the index and the
> working tree.
>
>
> Switching branches
> ------------------
>
> Along with the traditional two-way merge by `read-tree -m -u`,
> we would need to look at:
>
> . `bind` lines in the current `HEAD` commit.
>
> . `bind` lines in the commit we are switching to.
>
> . subproject binding information in the index file.
>
> to make sure we do sensible things.
>
> Just like until very recently we did not allow switching
> branches when two-way merge would lose local changes, we can
> start by refusing to switch branches when the subprojects bound
> in the index do not match what is recorded in the `HEAD` commit.
>
> Because in this round of the proposal we do not use the
> `$GIT_DIR/bind` file nor separate branches to keep track of
> heads of the subprojects, there is nothing else other than the
> working tree and the index file that needs to be updated when
> switching branches.
>
>
> Merging
> -------
>
> Merging two branches of the toplevel projects can use the
> traditional merging mechanism mostly unchanged. The merge base
> computation can be done using the `parent` ancestry information
> taken from the two toplevel project branch heads being merged,
> and merging of the whole tree can be done with a three-way merge
> of the whole tree using the merge base and two head commits.
> For reasons described later, we would not merge the subproject
> parts of the trees during this step, though.
>
> When the two branch heads use different versions of subproject,
> things get a bit tricky. First, let's forget for a moment about
> the case where they bind the same project at different location.
> We would refuse if they do not have the same number of `bind`
> lines that bind something at the same subdirectories.
>
> ------------
> $ git merge 'Merge in a side branch' HEAD side
> error: the merged heads have subprojects bound at different places.
> ours:
> linux-2.6/
> appliance/
> theirs:
> kernel/
> gadget/
> manual/
> ------------
>
> Such renaming can be handled by first moving the bind points in
> our branch, and redoing the merge (this is a rare operation
> anyway). It might go like this:
>
> ------------
> $ git bind-projects \
> $kernel_commit kernel/ \
> $gadget_commit gadget/
> $ git commit -m 'Prepare for merge with side branch'
> $ git merge 'Merge in a side branch' HEAD side
> error: the merged heads have subprojects bound at different places.
> ours:
> kernel/
> gadget/
> theirs:
> kernel/
> gadget/
> manual/
> ------------
>
> Their branch added another subproject, so this did not work (or
> it could be the other way around -- we might have been the one
> with `manual/` subproject while they didn't). This suggests
> that we may want an option to `git merge` to allow taking a
> union of subprojects. Again, this is a rare operation, and
> always taking a union would have created a toplevel project that
> had both `kernel/` and `linux-2.6/` bound to the same Linux
> kernel project from possibly different vintage, so it would be
> prudent to require the set of bound subprojects to exactly match
> and give the user an option to take a union.
>
> ------------
> $ git merge --union-subprojects 'Merge in a side branch HEAD side
> error: the subproject at `kernel/` needs to be merged first.
> ------------
>
> Here, the version of the Linux kernel project in the `side`
> branch was different from what our branch had on our `bind`
> line. On what kind of difference should we give this error?
> Initially, I think we could require one is the fast forward of
> the other (ours might be ahead of theirs, or the other way
> around), and take the descendant.
>
> Or we could do an independent merge of subprojects heads, using
> the `parent` ancestry of the bound subproject heads to find
> their merge-base and doing a three-way merge. This would leave
> the merge result in the subproject part of the working tree and
> the index.
>
> [NOTE]
> This is the reason we did not do the whole-tree three way merge
> earlier. The subproject commit bound to the merge base commit
> used for the toplevel project may not be the merge base between
> the subproject commits bound to the two toplevel project
> commits.
>
> So let's deal with the case to merge only a subproject part into
> our tree first.
>
>
> Merging subprojects
> -------------------
>
> An operation of more practical importance is to be able to merge
> in changes done outside to the projects bound to our toplevel
> project.
>
> ------------
> $ git pull --subproject=kernel/ git://git.kernel.org/.../linux-2.6/
> ------------
>
> might do:
>
> . fetch the current `HEAD` commit from Linus.
> . find the subproject commit bound at kernel/ subtree.
> . perform the usual three-way merge of these two commits, in
> `kernel/` part of the working tree.
>
> After that, `git commit \--subproject` option would be needed to
> make a commit.
>
> [NOTE]
> This suggests that we would need to have something similar to
> `MERGE_HEAD` for merging the subproject part.
>
>
> -
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Due to a shortage of devoted followers, the production of great leaders has been discontinued.
^ permalink raw reply
* Re: Notes on Subproject Support
From: Junio C Hamano @ 2006-01-23 8:38 UTC (permalink / raw)
To: git
In-Reply-To: <7v64ob1omh.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> writes:
> BTW, let's digress a bit.
Ugh. Serious typo.
> I think recording "commit" in the tree objects is in line with
> the logical organization described in README: "blob" and "tree"
> represent a state, and have *nothing* to do with how we came
> about to that state. The historyh is described in "commit"
> objects. The bound commit approach keeps that property.
Obviously, I think "*NOT* recording commit in tree objects" is
in line with "blobs and trees are about states, commits give
them their points in history".
^ permalink raw reply
* Re: [RFC] 1st pass at git-annotate (not-quite functional, however)
From: Junio C Hamano @ 2006-01-23 8:24 UTC (permalink / raw)
To: Ryan Anderson; +Cc: git
In-Reply-To: <20060123080532.GD19212@mythryan2.michonline.com>
Ryan Anderson <ryan@michonline.com> writes:
> I haven't figured out exactly how I want to handle annotating across
> merges,...
You might want to take a look at the "passing blames around"
algorithm I did for "git blame" loooooooooooooooong time ago.
http://article.gmane.org/gmane.comp.version-control.git/5483
^ permalink raw reply
* [RFC] 1st pass at git-annotate (not-quite functional, however)
From: Ryan Anderson @ 2006-01-23 8:05 UTC (permalink / raw)
To: git; +Cc: Linus Torvalds
Here's my stab at getting git-annotate working. This requires the
--remove-empty addition to git-rev-list Linus sent out a few days ago.
This code is not complete, but on my test case (git-format-mbox.sh) it
does track through renames, and does figure out authorship of about half
the lines (rough guess).
I have learned that parsing an arbritrary length series of diffs is
hard, mostly because trying to infer line numbers from them is tricky
(especially when you contemplate tracking state down multiple merges
at once.)
I currently have a very naive approach of tracking offsets in here,
built as the diff is parsed. I know it's wrong, but it's not too wrong,
if you ignore merges.
I haven't figured out exactly how I want to handle annotating across
merges, but I think the general idea I have here will work, it just
needs to be ripped out of global variables and stuck into some
structures that get passed around/split/merged up/etc.
I'm posting this tonight because I won't have time to look at this again
for a few days (probably the weekend), and someone else might see a way
to make this work a bit easier.
Without further ado, git-annotate:
Makefile | 2
git-annotate.perl | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 310 insertions(+), 1 deletions(-)
create mode 100755 git-annotate.perl
df7b8b7bf682411485aefa31c6dc24a4b8adf316
diff --git a/Makefile b/Makefile
index 6517204..505b353 100644
--- a/Makefile
+++ b/Makefile
@@ -118,7 +118,7 @@ SCRIPT_PERL = \
git-archimport.perl git-cvsimport.perl git-relink.perl \
git-shortlog.perl git-fmt-merge-msg.perl \
git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \
- git-graft-ripple.perl
+ git-graft-ripple.perl git-annotate.perl
SCRIPT_PYTHON = \
git-merge-recursive.py
diff --git a/git-annotate.perl b/git-annotate.perl
new file mode 100755
index 0000000..044828f
--- /dev/null
+++ b/git-annotate.perl
@@ -0,0 +1,309 @@
+#!/usr/bin/perl
+# Copyright 2006, Ryan Anderson <ryan@michonline.com>
+#
+# GPL v2 (See COPYING)
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+use warnings;
+use strict;
+
+use Data::Dumper;
+
+my $filename = shift @ARGV;
+
+
+my @stack = (
+ {
+ 'rev' => "HEAD",
+ 'filename' => $filename,
+ },
+);
+
+our (@lineoffsets, @pendinglineoffsets);
+my @filelines = ();
+open(F,"<",$filename)
+ or die "Failed to open filename: $!";
+
+while(<F>) {
+ chomp;
+ push @filelines, $_;
+}
+close(F);
+
+my $revsprocessed = 0;
+W: while (my $bound = pop @stack) {
+ my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'});
+ foreach my $revinst (@revisions) {
+ my ($rev, @parents) = @$revinst;
+ #last W if $revsprocessed++ > 3;
+
+ if (scalar @parents > 0) {
+ git_diff_parse($parents[0], $rev, $bound->{'filename'});
+ next;
+ }
+
+ printf("Boundary = %s\n", $rev);
+ my $newbound = find_parent_renames($rev, $bound->{'filename'});
+ if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) {
+ push @stack, $newbound;
+ }
+ }
+}
+
+my $i = 0;
+foreach my $l (@filelines) {
+ my ($output, $rev, $committer, $date);
+ if (ref $l eq 'ARRAY') {
+ ($output, $rev, $committer, $date) = @$l;
+ if (length($rev) > 8) {
+ $rev = substr($rev,0,8);
+ }
+ } else {
+ $output = $l;
+ ($rev, $committer, $date) = ('unknonw', 'unknown', 'unknown');
+ }
+
+ printf("(%8s %10s %10s %d)%s\n", $rev, $committer, $date, $i++, $output);
+}
+
+#print Data::Dumper->Dump([\@lineoffsets],[qw(*lineoffsets)]);
+
+
+sub git_rev_list {
+ my ($rev, $file) = @_;
+ #printf("grl = %s, %s\n", $rev, $file);
+
+ open(P,"-|","git-rev-list","--parents","--remove-empty",$rev,"--",$file)
+ or die "Failed to exec git-rev-list: $!";
+
+ my @revs;
+ while(my $line = <P>) {
+ chomp $line;
+ my ($rev, @parents) = split /\s+/, $line;
+ push @revs, [ $rev, @parents ];
+ }
+ close(P);
+
+ return @revs;
+}
+
+sub find_parent_renames {
+ my ($rev, $file) = @_;
+
+ open(P,"-|","git-diff", "-r","--name-status", "-z","$rev^1..$rev")
+ or die "Failed to exec git-diff: $!";
+
+ local $/ = "\0";
+ my %bound;
+ while (my $change = <P>) {
+ chomp $change;
+ my $filename = <P>;
+ chomp $filename;
+
+ if ($change =~ m/^[AMD]$/ ) {
+ next;
+ } elsif ($change =~ m/^R/ ) {
+ my $oldfilename = $filename;
+ $filename = <P>;
+ chomp $filename;
+ if ( $file eq $filename ) {
+ my $parent = git_find_parent($rev);
+ #printf("Found rename at boundary: %s-%s, %s\n", $rev, $parent, $oldfilename);
+ @bound{'rev','filename'} = ($parent, $oldfilename);
+
+ last;
+ } else {
+ #printf("Found unknown rename of %s => %s\n", $oldfilename, $filename);
+ }
+ } else {
+ #printf("Unknown name-status type of '%s'\n", $change);
+ }
+
+
+
+ }
+ close(P);
+
+ return \%bound;
+}
+
+
+sub git_find_parent {
+ my ($rev) = @_;
+
+ open(REVPARENT,"-|","git-rev-list","--parents","$rev^1..$rev")
+ or die "Failed to open git-rev-list to find a single parent: $!";
+
+ my $parentline = <REVPARENT>;
+ chomp $parentline;
+ my ($revfound,$parent) = split m/\s+/, $parentline;
+
+ close(REVPARENT);
+
+ return $parent;
+}
+
+
+# Get a diff between the current revision and a parent.
+# Record the commit information that results.
+sub git_diff_parse {
+ my ($parent, $rev, $filename) = @_;
+
+ my %revinfo = git_commit_info($rev);
+
+ # To make this slightly simpler, we're going to do things backwards.
+ # git-diff $rev..$parent gets us line numbers that pertain to $rev, not
+ # $parent, and we can work with those easier than the reverse.
+ open(DIFF,"-|","git-diff","$rev..$parent",$filename)
+ or die "Failed to call git-diff for annotation: $!";
+
+ my ($gotheader) = (0);
+ my ($remstart, $remlength, $addstart, $addlength);
+ my ($hunk_actual_start, $hunk_index, $hunk_adds);
+ while(<DIFF>) {
+ print;
+ if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) {
+ if (0 && $gotheader) {
+ print "Filelines had:\n";
+ for (my $i = $remstart ; $i < $remstart + $remlength; $i++) {
+ print $filelines[$i];
+
+ }
+ print "===================================\n";
+ }
+ ($remstart, $remlength, $addstart, $addlength) = ($1, $2, $3, $4);
+ # Adjust for 0-based arrays
+ $remstart--;
+ $addstart--;
+
+ # Record (but don't put into use right away) these offsets.
+ insert_into_lineoffsets( $remstart + $remlength, $remlength - $addlength );
+ $hunk_index = 0;
+ $hunk_adds = 0;
+ $hunk_actual_start = find_actual_start($remstart);
+ $gotheader = 1;
+ next;
+ } elsif (!$gotheader) {
+ next;
+ }
+
+ if (m/^\+/) {
+ next;
+
+ } elsif (m/^-(.*)$/) {
+ my $line = $1;
+ my $floffset = $hunk_actual_start + $hunk_index;
+
+ printf("\tline = %s\n\tfilelines entry = %s*\thas = %d, hi = %d\n",
+ $line, $filelines[$floffset],
+ $hunk_actual_start, $hunk_index) if 0;
+
+ if (ref $filelines[$floffset] eq '' && $filelines[$floffset] eq $line) {
+ my $oline = $filelines[$floffset];
+ $filelines[$floffset] =
+ [ $oline, $rev, $revinfo{'committer'}, $revinfo{'committer_date'} ];
+ }
+ }
+
+ $hunk_index++;
+ }
+ close(DIFF);
+
+ # Store these offsets for use when processing the next diff.
+ sync_line_offsets();
+}
+
+sub insert_into_lineoffsets {
+ my ($start, $offset) = @_;
+
+ push @pendinglineoffsets, [$start, $offset];
+
+}
+
+sub sync_line_offsets {
+ check_line_offsets(0);
+ #print Data::Dumper->Dump([\@pendinglineoffsets],[qw(*pending)]);
+ foreach my $o (@pendinglineoffsets) {
+ _insert_into_lineoffsets($$o[0],$$o[1]);
+ }
+ @pendinglineoffsets = ();
+
+ #print Data::Dumper->Dump([\@lineoffsets], [qw(*lineoffsets)]);
+ check_line_offsets(1);
+
+ if (defined $lineoffsets[0] && !defined $lineoffsets[0][0]) {
+ shift @lineoffsets;
+ }
+}
+
+sub check_line_offsets {
+ my $v = shift;
+ for(my $i = 0; $i < @lineoffsets; $i++) {
+ warn "lineoffsets[$i] undef at $v" if (!defined $lineoffsets[$i]);
+ warn "lineoffsets[$i][0] undef at $v" if (!defined $lineoffsets[$i][0]);
+ }
+}
+
+my $calls_to_lineoffsets = 1;
+sub _insert_into_lineoffsets {
+ my ($start, $offset) = @_;
+ #warn sprintf("lineoffsets has %d elements (%d)", scalar @lineoffsets, $calls_to_lineoffsets++);
+
+ if (@lineoffsets == 0) {
+ push @lineoffsets, [ $start, $offset ];
+ return;
+ }
+
+ # replace this with a binary search later ## FIXME ##
+ my $i = 0;
+ while ($i < @lineoffsets && $start < $lineoffsets[$i][0]) {
+ $i++;
+ }
+
+ if ($i < @lineoffsets && $start == $lineoffsets[$i][0]) {
+ $lineoffsets[$i][1] += $offset;
+ } else {
+ push @lineoffsets, [ $start, $offset ];
+ @lineoffsets = sort { $a->[0] <=> $b->[0] } @lineoffsets;
+ }
+}
+
+sub find_actual_start {
+ my ($start) = @_;
+ my $offset = 0;
+
+ for(my $i = 0; $i < @lineoffsets && $start < $lineoffsets[$i][0] ; $i++) {
+ $offset += $lineoffsets[$i][1];
+ }
+
+ printf("hunk_actual_start = %d (%d + %d)\n", $start + $offset, $start, $offset);
+ return $start + $offset;
+}
+
+
+sub git_commit_info {
+ my ($rev) = @_;
+ open(COMMIT, "-|","git-cat-file", "commit", $rev)
+ or die "Failed to call git-cat-file: $!";
+
+ my %info;
+ while(<COMMIT>) {
+ chomp;
+ last if (length $_ == 0);
+
+ if (m/^author (.*) <(.*)> (.*)$/) {
+ $info{'author'} = $1;
+ $info{'author_email'} = $2;
+ $info{'author_date'} = $3;
+ } elsif (m/^committer (.*) <(.*)> (.*)$/) {
+ $info{'committer'} = $1;
+ $info{'committer_email'} = $2;
+ $info{'committer_date'} = $3;
+ }
+ }
+ close(COMMIT);
+
+ return %info;
+}
--
1.1.4.g31e9b
--
Ryan Anderson
sometimes Pug Majere
^ permalink raw reply related
* Re: Notes on Subproject Support
From: Junio C Hamano @ 2006-01-23 8:00 UTC (permalink / raw)
To: git
In-Reply-To: <7v3bjfafql.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> writes:
> This is still a draft/WIP, but "release early" is a good
> discipline, so...
Tentatively I'm placing this document in the 'todo' branch, so
that people interested in the changes can ask gitweb to show
diffs, until I can find a better way and location to manage it.
I do not think it is suitable to be in the Documentation/ area,
due to its being an early draft and its just-one-ofthe-proposals
status.
^ permalink raw reply
* Re: Notes on Subproject Support
From: Alexander Litvinov @ 2006-01-23 6:06 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Daniel Barkalow, git, Petr Baudis
In-Reply-To: <7v64ob1omh.fsf@assigned-by-dhcp.cox.net>
In our development we use a little bit other case (it is simplified):
1. We have self written C++ library for linked list implementation. Lets call
it liblist.
2. We have project A that use liblist as separate directory and project B that
use this library too.
Currently we have 3 cvs projects with cvs-modules for linking liblist to A and
B. During development of A and B we often modify liblist to fix bugs and
these changes are immidatly visible to all projects who use liblist.
After full implementation of bind functionality I see one restriction: I have
to use one repo for storing all three projects: A, B and liblist to make
changes of liblist visible to all projects. The solution is to make separate
repos and on each change of liblist in prokect A push these changes to
liblist repo and pull them into project B again - bit hacky solution.
^ permalink raw reply
* Re: Notes on Subproject Support
From: Junio C Hamano @ 2006-01-23 5:48 UTC (permalink / raw)
To: Daniel Barkalow; +Cc: git, Petr Baudis
In-Reply-To: <7v7j8r7e7s.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> writes:
> In other words, I think the design so far does not require us to
> touch tree objects at all, and I'd be happy if we do not have to.
>
> One reason I started the bound commit approach was exactly
> because I only needed to muck with commit objects and did not
> have to touch trees and blobs; after trying to implement the
> core level for "gitlink", which I ended up touching quite a lot
> and have abandoned for now.
BTW, let's digress a bit.
I think recording "commit" in the tree objects is in line with
the logical organization described in README: "blob" and "tree"
represent a state, and have *nothing* to do with how we came
about to that state. The historyh is described in "commit"
objects. The bound commit approach keeps that property.
The "gitlink" approach, as I understand how Linus outlined in
his original suggestion, is a bit different. The link objects
appear in tree objects, and when you "git cat-file link" one of
them, you would see something like this:
commit 5b2bcc7b2d546c636f79490655b3347acc91d17f
name kernel
So in that sense, "gitlink" approach departs from the original
premise of "commit" being the only thing that ties things
together. Tree objects with "gitlink" know where they are in
the history [*1*].
By this, I do not mean to say that "gitlink" approach is
inferior because it breaks that original premise. I am just
pointing it out as a difference between two approaches.
Now, the current way index file is used is as a staging area to
create a new commit on top of the tip of the current branch.
However, it is interesting to note that logically, by itself
*alone*, it cannot be used that way. The information the index
file records is something that can be used to write out a tree
object, and not a commit, because it does not know where the
current state sits in the history. We have two separate files,
$GIT_DIR/HEAD that records which branch we are on, and the
branch head ref the HEAD points at, which records where the
current index came from, for that purpose. The latter tells us
what commit we should use as the parent commit if we create such
a commit, and the former tells us which branch head to update
once we create one. So in that sense, the index file is just a
staging area to create a new tree, not a new commit.
We could have done things differently. I am not advocating to
do the following change, but offering a possibility as a thought
experiment. It just felt interesting enough to point them out.
The index file could have recorded what commit the current state
recorded in the index came from. By recording the commit the
index was read from in the index itself, independently from the
$GIT_DIR/refs/heads/$branch file, we could have been able to
allow fetching into the current branch. When the $branch file
for the current branch was updated by a fast-forward fetch, we
would notice that the commit recorded there no longer match what
is recorded in the index.
Another interesting consequence is if the development is a
single repository and linear, we did not even need any file in
$GIT_DIR/refs/ ("branchless git"). The commit recorded as the
topmost in the index file itself would have served as the tip of
the development, and we would have been able to tangle the
history starting from the commit in the index file.
While we are doing a thought experiment, let's say we allow to
record more than one commits the current index is based upon.
'git merge' would record all the parent commits there, so that
writing out the merge result out of the index file as a tree and
then recording these commits as parents would have been the way
to create a merge commit. We would not need the auxiliary file
$GIT_DIR/MERGE_HEAD if we did so.
In other words, if the index file recorded the commits its
contents were based upon, instead of being a staging area for a
new tree, it would have been a staging area for a new commit.
Now, the latest proposal, borrowing your idea, records the
subproject commits bound to subdirectories in the index itself.
This is halfway to make the index file a staging area for the
next commit. If we were to do that, we also *could* record the
commits the current index is based upon, so that it can truly be
used as a staging area to create a new commit, not just a tree.
On the other hand, this could be a reason *not* to do the
`update-index --bind` to record the subproject information in
the index file. An auxiliary file such as $GIT_DIR/bind might
be sufficient, just like $GIT_DIR/MERGE_HEAD has been good
enough for us so far. One difference between MERGE_HEAD and
bind is that the former is very transient -- only exists during
a merge while the latter is persistent while the top commit is
checked out and being worked on.
[Footnote]
*1* One good property of "gitlink" approach is that we *could*
extend this blob-like object to store arbitrary human readable
information to represent a point-in-time from an arbitrary
foreign SCM system. IOW, we do not necessarily have to require
`commit` line that name a git commit to be there. It could say
"Please slurp http://www.kernel.org/pub/software/.../git.tar.gz
and extract it in git/ directory".
Of course, for such a toplevel project commit, the tool may not
be able to do a checkout automatically and require the user to
cat-file the link, download a tarball and extract the subtree
there manually.
The bound commit approach requires you to have git commit object
names on the `bind` lines, and it is fundamentally much harder
to extend it to allow interfacing with foreign (non-)SCM
systems.
^ permalink raw reply
* Re: [PATCH] new tutorial
From: J. Bruce Fields @ 2006-01-23 4:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v64olysw2.fsf@assigned-by-dhcp.cox.net>
On Sun, Jan 15, 2006 at 03:26:53PM -0800, Junio C Hamano wrote:
> I have a mixed feeling on this one. I tend to recommend --soft
> instead --mixed to people, but both have their pros and cons:
Yeah. My goal here is to pare this down to the minimum of text required
to get people up and running and having fun. To that end, I'd rather
just not explain what the index file is or how it's used. And really
git-reset could probably be left out entirely. So I've cut most of this
out, and left just one mention of git-reset --hard.
> If this is intended to be a beginner documentation, I'd prefer
> if it did not talk about cat-file, nor how commit objects are
> internally represented.
Fair enough. I was trying to find a way to show the structure of the
commit graph. But that was probably overkill.
So I've cut that out. I've also left out discussion of the SHA1's
entirely, though we might want to add that back in at some point. For
now it seems you can do enough without paying attention to them.
> These days by default "git fetch" (and "git pull") fetches the
> tags that refer to the commits you fetch as part of regular
> updates, so this part is mostly redundant.
Got it.
> BTW^2, it might be interesting to do
>
> $ git format-patch -C origin..master
>
> the next time around.
Here's take two.--b.
The current Documentation/tutorial.txt concentrates on the lower-level
git interfaces. So it's useful to people developing alternative
porcelains, to advanced users, etc., but not so much to beginning users.
I think it makes sense for the main tutorial to address those
beginnning users, so with this patch I'm proposing that we move
Documentation/tutorial.txt to Documentation/core-tutorial.txt and
replace it by a new tutorial.
Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu>
---
Documentation/core-tutorial.txt | 0
Documentation/tutorial.txt | 2023 ++++++---------------------------------
2 files changed, 303 insertions(+), 1720 deletions(-)
copy Documentation/{tutorial.txt => core-tutorial.txt} (100%)
99f6a432f3a4da666d3f535e78cba04085aa1b46
diff --git a/Documentation/tutorial.txt b/Documentation/core-tutorial.txt
similarity index 100%
copy from Documentation/tutorial.txt
copy to Documentation/core-tutorial.txt
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index b8fa299..a09bbea 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -1,1821 +1,404 @@
-A short git tutorial
-====================
+A tutorial introduction to git
+==============================
-Introduction
-------------
+This tutorial explains how to import a new project into git, make
+changes to it, and share changes with other developers.
-This is trying to be a short tutorial on setting up and using a git
-repository, mainly because being hands-on and using explicit examples is
-often the best way of explaining what is going on.
+First, note that you can get documentation for a command such as "git
+diff" with:
-In normal life, most people wouldn't use the "core" git programs
-directly, but rather script around them to make them more palatable.
-Understanding the core git stuff may help some people get those scripts
-done, though, and it may also be instructive in helping people
-understand what it is that the higher-level helper scripts are actually
-doing.
+------------------------------------------------
+$ man git-diff
+------------------------------------------------
-The core git is often called "plumbing", with the prettier user
-interfaces on top of it called "porcelain". You may not want to use the
-plumbing directly very often, but it can be good to know what the
-plumbing does for when the porcelain isn't flushing.
+Importing a new project
+-----------------------
-The material presented here often goes deep describing how things
-work internally. If you are mostly interested in using git as a
-SCM, you can skip them during your first pass.
+Assume you have a tarball project.tar.gz with your initial work. You
+can place it under git revision control as follows.
-[NOTE]
-And those "too deep" descriptions are often marked as Note.
+------------------------------------------------
+$ tar -xzf project.tar.gz
+$ cd project
+$ git init-db
+------------------------------------------------
-[NOTE]
-If you are already familiar with another version control system,
-like CVS, you may want to take a look at
-link:everyday.html[Everyday GIT in 20 commands or so] first
-before reading this.
+Git will reply
+------------------------------------------------
+defaulting to local storage area
+------------------------------------------------
-Creating a git repository
--------------------------
+You've now initialized the working directory--you may notice a new
+directory created, named ".git". Tell git that you want it to track
+every file under the current directory with
-Creating a new git repository couldn't be easier: all git repositories start
-out empty, and the only thing you need to do is find yourself a
-subdirectory that you want to use as a working tree - either an empty
-one for a totally new project, or an existing working tree that you want
-to import into git.
+------------------------------------------------
+$ git add .
+------------------------------------------------
-For our first example, we're going to start a totally new repository from
-scratch, with no pre-existing files, and we'll call it `git-tutorial`.
-To start up, create a subdirectory for it, change into that
-subdirectory, and initialize the git infrastructure with `git-init-db`:
+Finally,
------------------------------------------------
-$ mkdir git-tutorial
-$ cd git-tutorial
-$ git-init-db
+$ git commit -a
------------------------------------------------
-to which git will reply
+will prompt you for a commit message, then record the current state
+of all the files to the repository.
-----------------
-defaulting to local storage area
-----------------
+Try modifying some files, then run
-which is just git's way of saying that you haven't been doing anything
-strange, and that it will have created a local `.git` directory setup for
-your new project. You will now have a `.git` directory, and you can
-inspect that with `ls`. For your new empty project, it should show you
-three entries, among other things:
-
- - a symlink called `HEAD`, pointing to `refs/heads/master` (if your
- platform does not have native symlinks, it is a file containing the
- line "ref: refs/heads/master")
-+
-Don't worry about the fact that the file that the `HEAD` link points to
-doesn't even exist yet -- you haven't created the commit that will
-start your `HEAD` development branch yet.
-
- - a subdirectory called `objects`, which will contain all the
- objects of your project. You should never have any real reason to
- look at the objects directly, but you might want to know that these
- objects are what contains all the real 'data' in your repository.
-
- - a subdirectory called `refs`, which contains references to objects.
-
-In particular, the `refs` subdirectory will contain two other
-subdirectories, named `heads` and `tags` respectively. They do
-exactly what their names imply: they contain references to any number
-of different 'heads' of development (aka 'branches'), and to any
-'tags' that you have created to name specific versions in your
-repository.
-
-One note: the special `master` head is the default branch, which is
-why the `.git/HEAD` file was created as a symlink to it even if it
-doesn't yet exist. Basically, the `HEAD` link is supposed to always
-point to the branch you are working on right now, and you always
-start out expecting to work on the `master` branch.
-
-However, this is only a convention, and you can name your branches
-anything you want, and don't have to ever even 'have' a `master`
-branch. A number of the git tools will assume that `.git/HEAD` is
-valid, though.
-
-[NOTE]
-An 'object' is identified by its 160-bit SHA1 hash, aka 'object name',
-and a reference to an object is always the 40-byte hex
-representation of that SHA1 name. The files in the `refs`
-subdirectory are expected to contain these hex references
-(usually with a final `\'\n\'` at the end), and you should thus
-expect to see a number of 41-byte files containing these
-references in these `refs` subdirectories when you actually start
-populating your tree.
-
-[NOTE]
-An advanced user may want to take a look at the
-link:repository-layout.html[repository layout] document
-after finishing this tutorial.
+------------------------------------------------
+$ git diff
+------------------------------------------------
-You have now created your first git repository. Of course, since it's
-empty, that's not very useful, so let's start populating it with data.
+to review your changes. When you're done,
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
-Populating a git repository
----------------------------
+will again prompt your for a message describing the change, and then
+record the new versions of the modified files.
-We'll keep this simple and stupid, so we'll start off with populating a
-few trivial files just to get a feel for it.
+A note on commit messages: Though not required, it's a good idea to
+begin the commit message with a single short (less than 50 character)
+line summarizing the change, followed by a blank line and then a more
+thorough description. Tools that turn commits into email, for
+example, use the first line on the Subject line and the rest of the
+commit in the body.
-Start off with just creating any random files that you want to maintain
-in your git repository. We'll start off with a few bad examples, just to
-get a feel for how this works:
+To add a new file, first create the file, then
------------------------------------------------
-$ echo "Hello World" >hello
-$ echo "Silly example" >example
+$ git add path/to/new/file
------------------------------------------------
-you have now created two files in your working tree (aka 'working directory'), but to
-actually check in your hard work, you will have to go through two steps:
+then commit as usual. No special command is required when removing a
+file; just remove it, then commit.
- - fill in the 'index' file (aka 'cache') with the information about your
- working tree state.
+At any point you can view the history of your changes using
- - commit that index file as an object.
+------------------------------------------------
+$ git whatchanged
+------------------------------------------------
-The first step is trivial: when you want to tell git about any changes
-to your working tree, you use the `git-update-index` program. That
-program normally just takes a list of filenames you want to update, but
-to avoid trivial mistakes, it refuses to add new entries to the index
-(or remove existing ones) unless you explicitly tell it that you're
-adding a new entry with the `\--add` flag (or removing an entry with the
-`\--remove`) flag.
+If you also want to see complete diffs at each step, use
-So to populate the index with the two files you just created, you can do
+------------------------------------------------
+$ git whatchanged -p
+------------------------------------------------
+
+Managing branches
+-----------------
+
+A single git repository can maintain multiple branches of
+development. To create a new branch named "experimental", use
------------------------------------------------
-$ git-update-index --add hello example
+$ git branch experimental
------------------------------------------------
-and you have now told git to track those two files.
+If you now run
-In fact, as you did that, if you now look into your object directory,
-you'll notice that git will have added two new objects to the object
-database. If you did exactly the steps above, you should now be able to do
+------------------------------------------------
+$ git branch
+------------------------------------------------
+you'll get a list of all existing branches:
-----------------
-$ ls .git/objects/??/*
-----------------
+------------------------------------------------
+ experimental
+* master
+------------------------------------------------
-and see two files:
+The "experimental" branch is the one you just created, and the
+"master" branch is a default branch that was created for you
+automatically. The asterisk marks the branch you are currently on;
+type
-----------------
-.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
-.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
-----------------
+------------------------------------------------
+$ git checkout experimental
+------------------------------------------------
-which correspond with the objects with names of 557db... and f24c7..
-respectively.
+to switch to the experimental branch. Now edit a file, commit the
+change, and switch back to the master branch:
-If you want to, you can use `git-cat-file` to look at those objects, but
-you'll have to use the object name, not the filename of the object:
+------------------------------------------------
+(edit file)
+$ git commit -a
+$ git checkout master
+------------------------------------------------
-----------------
-$ git-cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
-----------------
+Check that the change you made is no longer visible, since it was
+made on the experimental branch and you're back on the master branch.
-where the `-t` tells `git-cat-file` to tell you what the "type" of the
-object is. git will tell you that you have a "blob" object (ie just a
-regular file), and you can see the contents with
+You can make a different change on the master branch:
-----------------
-$ git-cat-file "blob" 557db03
-----------------
+------------------------------------------------
+(edit file)
+$ git commit -a
+------------------------------------------------
-which will print out "Hello World". The object 557db03 is nothing
-more than the contents of your file `hello`.
+at this point the two branches have diverged, with different changes
+made in each. To merge the changes made in the two branches, run
-[NOTE]
-Don't confuse that object with the file `hello` itself. The
-object is literally just those specific *contents* of the file, and
-however much you later change the contents in file `hello`, the object
-we just looked at will never change. Objects are immutable.
+------------------------------------------------
+$ git pull . experimental
+------------------------------------------------
-[NOTE]
-The second example demonstrates that you can
-abbreviate the object name to only the first several
-hexadecimal digits in most places.
+If the changes don't conflict, you're done. If there are conflicts,
+markers will be left in the problematic files showing the conflict;
-Anyway, as we mentioned previously, you normally never actually take a
-look at the objects themselves, and typing long 40-character hex
-names is not something you'd normally want to do. The above digression
-was just to show that `git-update-index` did something magical, and
-actually saved away the contents of your files into the git object
-database.
+------------------------------------------------
+$ git diff
+------------------------------------------------
-Updating the index did something else too: it created a `.git/index`
-file. This is the index that describes your current working tree, and
-something you should be very aware of. Again, you normally never worry
-about the index file itself, but you should be aware of the fact that
-you have not actually really "checked in" your files into git so far,
-you've only *told* git about them.
+will show this. Once you've edited the files to resolve the
+conflicts,
-However, since git knows about them, you can now start using some of the
-most basic git commands to manipulate the files or look at their status.
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
-In particular, let's not even check in the two files into git yet, we'll
-start off by adding another line to `hello` first:
+will commit the result of the merge. Finally,
------------------------------------------------
-$ echo "It's a new day for git" >>hello
+$ gitk
------------------------------------------------
-and you can now, since you told git about the previous state of `hello`, ask
-git what has changed in the tree compared to your old index, using the
-`git-diff-files` command:
+will show a nice graphical representation of the resulting history.
-------------
-$ git-diff-files
-------------
+If you develop on a branch crazy-idea, then regret it, you can always
+delete the branch with
-Oops. That wasn't very readable. It just spit out its own internal
-version of a `diff`, but that internal version really just tells you
-that it has noticed that "hello" has been modified, and that the old object
-contents it had have been replaced with something else.
+-------------------------------------
+$ git branch -D crazy-idea
+-------------------------------------
-To make it readable, we can tell git-diff-files to output the
-differences as a patch, using the `-p` flag:
+Branches are cheap and easy, so this is a good way to try something
+out.
-------------
-$ git-diff-files -p
-diff --git a/hello b/hello
-index 557db03..263414f 100644
---- a/hello
-+++ b/hello
-@@ -1 +1,2 @@
- Hello World
-+It's a new day for git
-----
+Using git for collaboration
+---------------------------
-i.e. the diff of the change we caused by adding another line to `hello`.
+Suppose that Alice has started a new project with a git repository in
+/home/alice/project, and that Bob, who has a home directory on the
+same machine, wants to contribute.
-In other words, `git-diff-files` always shows us the difference between
-what is recorded in the index, and what is currently in the working
-tree. That's very useful.
+Bob begins with:
-A common shorthand for `git-diff-files -p` is to just write `git
-diff`, which will do the same thing.
+------------------------------------------------
+$ git clone /home/alice/project myrepo
+------------------------------------------------
-------------
-$ git diff
-diff --git a/hello b/hello
-index 557db03..263414f 100644
---- a/hello
-+++ b/hello
-@@ -1 +1,2 @@
- Hello World
-+It's a new day for git
-------------
-
-
-Committing git state
---------------------
-
-Now, we want to go to the next stage in git, which is to take the files
-that git knows about in the index, and commit them as a real tree. We do
-that in two phases: creating a 'tree' object, and committing that 'tree'
-object as a 'commit' object together with an explanation of what the
-tree was all about, along with information of how we came to that state.
-
-Creating a tree object is trivial, and is done with `git-write-tree`.
-There are no options or other input: git-write-tree will take the
-current index state, and write an object that describes that whole
-index. In other words, we're now tying together all the different
-filenames with their contents (and their permissions), and we're
-creating the equivalent of a git "directory" object:
-
-------------------------------------------------
-$ git-write-tree
-------------------------------------------------
-
-and this will just output the name of the resulting tree, in this case
-(if you have done exactly as I've described) it should be
-
-----------------
-8988da15d077d4829fc51d8544c097def6644dbb
-----------------
-
-which is another incomprehensible object name. Again, if you want to,
-you can use `git-cat-file -t 8988d\...` to see that this time the object
-is not a "blob" object, but a "tree" object (you can also use
-`git-cat-file` to actually output the raw object contents, but you'll see
-mainly a binary mess, so that's less interesting).
-
-However -- normally you'd never use `git-write-tree` on its own, because
-normally you always commit a tree into a commit object using the
-`git-commit-tree` command. In fact, it's easier to not actually use
-`git-write-tree` on its own at all, but to just pass its result in as an
-argument to `git-commit-tree`.
-
-`git-commit-tree` normally takes several arguments -- it wants to know
-what the 'parent' of a commit was, but since this is the first commit
-ever in this new repository, and it has no parents, we only need to pass in
-the object name of the tree. However, `git-commit-tree`
-also wants to get a commit message
-on its standard input, and it will write out the resulting object name for the
-commit to its standard output.
-
-And this is where we create the `.git/refs/heads/master` file
-which is pointed at by `HEAD`. This file is supposed to contain
-the reference to the top-of-tree of the master branch, and since
-that's exactly what `git-commit-tree` spits out, we can do this
-all with a sequence of simple shell commands:
-
-------------------------------------------------
-$ tree=$(git-write-tree)
-$ commit=$(echo 'Initial commit' | git-commit-tree $tree)
-$ git-update-ref HEAD $commit
-------------------------------------------------
-
-which will say:
-
-----------------
-Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb
-----------------
-
-just to warn you about the fact that it created a totally new commit
-that is not related to anything else. Normally you do this only *once*
-for a project ever, and all later commits will be parented on top of an
-earlier commit, and you'll never see this "Committing initial tree"
-message ever again.
-
-Again, normally you'd never actually do this by hand. There is a
-helpful script called `git commit` that will do all of this for you. So
-you could have just written `git commit`
-instead, and it would have done the above magic scripting for you.
-
-
-Making a change
----------------
-
-Remember how we did the `git-update-index` on file `hello` and then we
-changed `hello` afterward, and could compare the new state of `hello` with the
-state we saved in the index file?
-
-Further, remember how I said that `git-write-tree` writes the contents
-of the *index* file to the tree, and thus what we just committed was in
-fact the *original* contents of the file `hello`, not the new ones. We did
-that on purpose, to show the difference between the index state, and the
-state in the working tree, and how they don't have to match, even
-when we commit things.
-
-As before, if we do `git-diff-files -p` in our git-tutorial project,
-we'll still see the same difference we saw last time: the index file
-hasn't changed by the act of committing anything. However, now that we
-have committed something, we can also learn to use a new command:
-`git-diff-index`.
-
-Unlike `git-diff-files`, which showed the difference between the index
-file and the working tree, `git-diff-index` shows the differences
-between a committed *tree* and either the index file or the working
-tree. In other words, `git-diff-index` wants a tree to be diffed
-against, and before we did the commit, we couldn't do that, because we
-didn't have anything to diff against.
-
-But now we can do
-
-----------------
-$ git-diff-index -p HEAD
-----------------
-
-(where `-p` has the same meaning as it did in `git-diff-files`), and it
-will show us the same difference, but for a totally different reason.
-Now we're comparing the working tree not against the index file,
-but against the tree we just wrote. It just so happens that those two
-are obviously the same, so we get the same result.
-
-Again, because this is a common operation, you can also just shorthand
-it with
-
-----------------
-$ git diff HEAD
-----------------
-
-which ends up doing the above for you.
-
-In other words, `git-diff-index` normally compares a tree against the
-working tree, but when given the `\--cached` flag, it is told to
-instead compare against just the index cache contents, and ignore the
-current working tree state entirely. Since we just wrote the index
-file to HEAD, doing `git-diff-index \--cached -p HEAD` should thus return
-an empty set of differences, and that's exactly what it does.
-
-[NOTE]
-================
-`git-diff-index` really always uses the index for its
-comparisons, and saying that it compares a tree against the working
-tree is thus not strictly accurate. In particular, the list of
-files to compare (the "meta-data") *always* comes from the index file,
-regardless of whether the `\--cached` flag is used or not. The `\--cached`
-flag really only determines whether the file *contents* to be compared
-come from the working tree or not.
-
-This is not hard to understand, as soon as you realize that git simply
-never knows (or cares) about files that it is not told about
-explicitly. git will never go *looking* for files to compare, it
-expects you to tell it what the files are, and that's what the index
-is there for.
-================
-
-However, our next step is to commit the *change* we did, and again, to
-understand what's going on, keep in mind the difference between "working
-tree contents", "index file" and "committed tree". We have changes
-in the working tree that we want to commit, and we always have to
-work through the index file, so the first thing we need to do is to
-update the index cache:
-
-------------------------------------------------
-$ git-update-index hello
-------------------------------------------------
-
-(note how we didn't need the `\--add` flag this time, since git knew
-about the file already).
-
-Note what happens to the different `git-diff-\*` versions here. After
-we've updated `hello` in the index, `git-diff-files -p` now shows no
-differences, but `git-diff-index -p HEAD` still *does* show that the
-current state is different from the state we committed. In fact, now
-`git-diff-index` shows the same difference whether we use the `--cached`
-flag or not, since now the index is coherent with the working tree.
-
-Now, since we've updated `hello` in the index, we can commit the new
-version. We could do it by writing the tree by hand again, and
-committing the tree (this time we'd have to use the `-p HEAD` flag to
-tell commit that the HEAD was the *parent* of the new commit, and that
-this wasn't an initial commit any more), but you've done that once
-already, so let's just use the helpful script this time:
-
-------------------------------------------------
-$ git commit
-------------------------------------------------
-
-which starts an editor for you to write the commit message and tells you
-a bit about what you have done.
-
-Write whatever message you want, and all the lines that start with '#'
-will be pruned out, and the rest will be used as the commit message for
-the change. If you decide you don't want to commit anything after all at
-this point (you can continue to edit things and update the index), you
-can just leave an empty message. Otherwise `git commit` will commit
-the change for you.
-
-You've now made your first real git commit. And if you're interested in
-looking at what `git commit` really does, feel free to investigate:
-it's a few very simple shell scripts to generate the helpful (?) commit
-message headers, and a few one-liners that actually do the
-commit itself (`git-commit`).
-
-
-Inspecting Changes
-------------------
-
-While creating changes is useful, it's even more useful if you can tell
-later what changed. The most useful command for this is another of the
-`diff` family, namely `git-diff-tree`.
-
-`git-diff-tree` can be given two arbitrary trees, and it will tell you the
-differences between them. Perhaps even more commonly, though, you can
-give it just a single commit object, and it will figure out the parent
-of that commit itself, and show the difference directly. Thus, to get
-the same diff that we've already seen several times, we can now do
-
-----------------
-$ git-diff-tree -p HEAD
-----------------
-
-(again, `-p` means to show the difference as a human-readable patch),
-and it will show what the last commit (in `HEAD`) actually changed.
-
-[NOTE]
-============
-Here is an ASCII art by Jon Loeliger that illustrates how
-various diff-\* commands compare things.
-
- diff-tree
- +----+
- | |
- | |
- V V
- +-----------+
- | Object DB |
- | Backing |
- | Store |
- +-----------+
- ^ ^
- | |
- | | diff-index --cached
- | |
- diff-index | V
- | +-----------+
- | | Index |
- | | "cache" |
- | +-----------+
- | ^
- | |
- | | diff-files
- | |
- V V
- +-----------+
- | Working |
- | Directory |
- +-----------+
-============
-
-More interestingly, you can also give `git-diff-tree` the `-v` flag, which
-tells it to also show the commit message and author and date of the
-commit, and you can tell it to show a whole series of diffs.
-Alternatively, you can tell it to be "silent", and not show the diffs at
-all, but just show the actual commit message.
-
-In fact, together with the `git-rev-list` program (which generates a
-list of revisions), `git-diff-tree` ends up being a veritable fount of
-changes. A trivial (but very useful) script called `git-whatchanged` is
-included with git which does exactly this, and shows a log of recent
-activities.
-
-To see the whole history of our pitiful little git-tutorial project, you
-can do
-
-----------------
-$ git log
-----------------
-
-which shows just the log messages, or if we want to see the log together
-with the associated patches use the more complex (and much more
-powerful)
-
-----------------
-$ git-whatchanged -p --root
-----------------
-
-and you will see exactly what has changed in the repository over its
-short history.
-
-[NOTE]
-The `\--root` flag is a flag to `git-diff-tree` to tell it to
-show the initial aka 'root' commit too. Normally you'd probably not
-want to see the initial import diff, but since the tutorial project
-was started from scratch and is so small, we use it to make the result
-a bit more interesting.
-
-With that, you should now be having some inkling of what git does, and
-can explore on your own.
-
-[NOTE]
-Most likely, you are not directly using the core
-git Plumbing commands, but using Porcelain like Cogito on top
-of it. Cogito works a bit differently and you usually do not
-have to run `git-update-index` yourself for changed files (you
-do tell underlying git about additions and removals via
-`cg-add` and `cg-rm` commands). Just before you make a commit
-with `cg-commit`, Cogito figures out which files you modified,
-and runs `git-update-index` on them for you.
+This creates a new directory "myrepo" containing a clone of Alice's
+repository. The clone is on an equal footing with the original
+project, posessing its own copy of the original project's history.
+Bob then makes some changes and commits them:
-Tagging a version
------------------
+------------------------------------------------
+(edit files)
+$ git commit -a
+(repeat as necessary)
+------------------------------------------------
+
+When he's ready, he tells Alice to pull changes from the repository
+at /home/bob/myrepo. She does this with:
+
+------------------------------------------------
+$ cd /home/alice/project
+$ git pull /home/bob/myrepo
+------------------------------------------------
+
+This actually pulls changes from the branch in Bob's repository named
+"master". Alice could request a different branch by adding the name
+of the branch to the end of the git pull command line.
+
+This merges Bob's changes into her repository; "git whatchanged" will
+now show the new commits. If Alice has made her own changes in the
+meantime, then Bob's changes will be merged in, and she will need to
+manually fix any conflicts.
-In git, there are two kinds of tags, a "light" one, and an "annotated tag".
+A more cautious Alice might wish to examine Bob's changes before
+pulling them. She can do this by creating a temporary branch just
+for the purpose of studying Bob's changes:
-A "light" tag is technically nothing more than a branch, except we put
-it in the `.git/refs/tags/` subdirectory instead of calling it a `head`.
-So the simplest form of tag involves nothing more than
-
-------------------------------------------------
-$ git tag my-first-tag
-------------------------------------------------
-
-which just writes the current `HEAD` into the `.git/refs/tags/my-first-tag`
-file, after which point you can then use this symbolic name for that
-particular state. You can, for example, do
-
-----------------
-$ git diff my-first-tag
-----------------
-
-to diff your current state against that tag (which at this point will
-obviously be an empty diff, but if you continue to develop and commit
-stuff, you can use your tag as an "anchor-point" to see what has changed
-since you tagged it.
-
-An "annotated tag" is actually a real git object, and contains not only a
-pointer to the state you want to tag, but also a small tag name and
-message, along with optionally a PGP signature that says that yes,
-you really did
-that tag. You create these annotated tags with either the `-a` or
-`-s` flag to `git tag`:
-
-----------------
-$ git tag -s <tagname>
-----------------
-
-which will sign the current `HEAD` (but you can also give it another
-argument that specifies the thing to tag, ie you could have tagged the
-current `mybranch` point by using `git tag <tagname> mybranch`).
-
-You normally only do signed tags for major releases or things
-like that, while the light-weight tags are useful for any marking you
-want to do -- any time you decide that you want to remember a certain
-point, just create a private tag for it, and you have a nice symbolic
-name for the state at that point.
-
-
-Copying repositories
---------------------
-
-git repositories are normally totally self-sufficient and relocatable
-Unlike CVS, for example, there is no separate notion of
-"repository" and "working tree". A git repository normally *is* the
-working tree, with the local git information hidden in the `.git`
-subdirectory. There is nothing else. What you see is what you got.
-
-[NOTE]
-You can tell git to split the git internal information from
-the directory that it tracks, but we'll ignore that for now: it's not
-how normal projects work, and it's really only meant for special uses.
-So the mental model of "the git information is always tied directly to
-the working tree that it describes" may not be technically 100%
-accurate, but it's a good model for all normal use.
-
-This has two implications:
-
- - if you grow bored with the tutorial repository you created (or you've
- made a mistake and want to start all over), you can just do simple
-+
-----------------
-$ rm -rf git-tutorial
-----------------
-+
-and it will be gone. There's no external repository, and there's no
-history outside the project you created.
-
- - if you want to move or duplicate a git repository, you can do so. There
- is `git clone` command, but if all you want to do is just to
- create a copy of your repository (with all the full history that
- went along with it), you can do so with a regular
- `cp -a git-tutorial new-git-tutorial`.
-+
-Note that when you've moved or copied a git repository, your git index
-file (which caches various information, notably some of the "stat"
-information for the files involved) will likely need to be refreshed.
-So after you do a `cp -a` to create a new copy, you'll want to do
-+
-----------------
-$ git-update-index --refresh
-----------------
-+
-in the new repository to make sure that the index file is up-to-date.
-
-Note that the second point is true even across machines. You can
-duplicate a remote git repository with *any* regular copy mechanism, be it
-`scp`, `rsync` or `wget`.
-
-When copying a remote repository, you'll want to at a minimum update the
-index cache when you do this, and especially with other peoples'
-repositories you often want to make sure that the index cache is in some
-known state (you don't know *what* they've done and not yet checked in),
-so usually you'll precede the `git-update-index` with a
-
-----------------
-$ git-read-tree --reset HEAD
-$ git-update-index --refresh
-----------------
-
-which will force a total index re-build from the tree pointed to by `HEAD`.
-It resets the index contents to `HEAD`, and then the `git-update-index`
-makes sure to match up all index entries with the checked-out files.
-If the original repository had uncommitted changes in its
-working tree, `git-update-index --refresh` notices them and
-tells you they need to be updated.
-
-The above can also be written as simply
-
-----------------
-$ git reset
-----------------
-
-and in fact a lot of the common git command combinations can be scripted
-with the `git xyz` interfaces. You can learn things by just looking
-at what the various git scripts do. For example, `git reset` is the
-above two lines implemented in `git-reset`, but some things like
-`git status` and `git commit` are slightly more complex scripts around
-the basic git commands.
-
-Many (most?) public remote repositories will not contain any of
-the checked out files or even an index file, and will *only* contain the
-actual core git files. Such a repository usually doesn't even have the
-`.git` subdirectory, but has all the git files directly in the
-repository.
-
-To create your own local live copy of such a "raw" git repository, you'd
-first create your own subdirectory for the project, and then copy the
-raw repository contents into the `.git` directory. For example, to
-create your own copy of the git repository, you'd do the following
-
-----------------
-$ mkdir my-git
-$ cd my-git
-$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
-----------------
-
-followed by
-
-----------------
-$ git-read-tree HEAD
-----------------
-
-to populate the index. However, now you have populated the index, and
-you have all the git internal files, but you will notice that you don't
-actually have any of the working tree files to work on. To get
-those, you'd check them out with
-
-----------------
-$ git-checkout-index -u -a
-----------------
-
-where the `-u` flag means that you want the checkout to keep the index
-up-to-date (so that you don't have to refresh it afterward), and the
-`-a` flag means "check out all files" (if you have a stale copy or an
-older version of a checked out tree you may also need to add the `-f`
-flag first, to tell git-checkout-index to *force* overwriting of any old
-files).
-
-Again, this can all be simplified with
-
-----------------
-$ git clone rsync://rsync.kernel.org/pub/scm/git/git.git/ my-git
-$ cd my-git
-$ git checkout
-----------------
-
-which will end up doing all of the above for you.
-
-You have now successfully copied somebody else's (mine) remote
-repository, and checked it out.
-
-
-Creating a new branch
----------------------
-
-Branches in git are really nothing more than pointers into the git
-object database from within the `.git/refs/` subdirectory, and as we
-already discussed, the `HEAD` branch is nothing but a symlink to one of
-these object pointers.
-
-You can at any time create a new branch by just picking an arbitrary
-point in the project history, and just writing the SHA1 name of that
-object into a file under `.git/refs/heads/`. You can use any filename you
-want (and indeed, subdirectories), but the convention is that the
-"normal" branch is called `master`. That's just a convention, though,
-and nothing enforces it.
-
-To show that as an example, let's go back to the git-tutorial repository we
-used earlier, and create a branch in it. You do that by simply just
-saying that you want to check out a new branch:
-
-------------
-$ git checkout -b mybranch
-------------
-
-will create a new branch based at the current `HEAD` position, and switch
-to it.
-
-[NOTE]
-================================================
-If you make the decision to start your new branch at some
-other point in the history than the current `HEAD`, you can do so by
-just telling `git checkout` what the base of the checkout would be.
-In other words, if you have an earlier tag or branch, you'd just do
-
-------------
-$ git checkout -b mybranch earlier-commit
-------------
-
-and it would create the new branch `mybranch` at the earlier commit,
-and check out the state at that time.
-================================================
+-------------------------------------
+$ git fetch /home/bob/myrepo master:bob-incoming
+-------------------------------------
+
+which fetches the changes from Bob's master branch into a new branch
+named bob-incoming. (Unlike git pull, git fetch just fetches a copy
+of Bob's line of development without doing any merging). Then
+
+-------------------------------------
+$ git whatchanged -p master..bob-incoming
+-------------------------------------
-You can always just jump back to your original `master` branch by doing
+shows a list of all the changes that Bob made since he branched from
+Alice's master branch.
-------------
+After examing those changes, and possibly fixing things, Alice can
+pull the changes into her master branch:
+
+-------------------------------------
$ git checkout master
-------------
+$ git pull . bob-incoming
+-------------------------------------
-(or any other branch-name, for that matter) and if you forget which
-branch you happen to be on, a simple
+The last command is a pull from the "bob-incoming" branch in Alice's
+own repository.
-------------
-$ ls -l .git/HEAD
-------------
+Later, Bob can update his repo with Alice's latest changes using
-will tell you where it's pointing (Note that on platforms with bad or no
-symlink support, you have to execute
+-------------------------------------
+$ git pull
+-------------------------------------
-------------
-$ cat .git/HEAD
-------------
+Note that he doesn't need to give the path to Alice's repository;
+when Bob cloned Alice's repository, git stored the location of her
+repository in the file .git/remotes/origin, and that location is used
+as the default for pulls.
-instead). To get the list of branches you have, you can say
+Bob may also notice a branch in his repository that he didn't create:
-------------
+-------------------------------------
$ git branch
-------------
+* master
+ origin
+-------------------------------------
-which is nothing more than a simple script around `ls .git/refs/heads`.
-There will be asterisk in front of the branch you are currently on.
+The "origin" branch, which was created automatically by "git clone",
+is a pristine copy of Alice's master branch; Bob should never commit
+to it.
-Sometimes you may wish to create a new branch _without_ actually
-checking it out and switching to it. If so, just use the command
+If Bob later decides to work from a different host, he can still
+perform clones and pulls using the ssh protocol:
-------------
-$ git branch <branchname> [startingpoint]
-------------
+-------------------------------------
+$ git clone alice.org:/home/alice/project myrepo
+-------------------------------------
-which will simply _create_ the branch, but will not do anything further.
-You can then later -- once you decide that you want to actually develop
-on that branch -- switch to that branch with a regular `git checkout`
-with the branchname as the argument.
+Alternatively, git has a native protocol, or can use rsync or http;
+see gitlink:git-pull[1] for details.
+Git can also be used in a CVS-like mode, with a central repository
+that various users push changes to; see gitlink:git-push[1] and
+link:cvs-migration.html[git for CVS users].
-Merging two branches
---------------------
+Keeping track of history
+------------------------
-One of the ideas of having a branch is that you do some (possibly
-experimental) work in it, and eventually merge it back to the main
-branch. So assuming you created the above `mybranch` that started out
-being the same as the original `master` branch, let's make sure we're in
-that branch, and do some work there.
+Git history is represented as a series of interrelated commits. The
+most recent commit in the currently checked-out branch can always be
+referred to as HEAD, and the "parent" of any commit can always be
+referred to by appending a caret, "^", to the end of the name of the
+commit. So, for example,
-------------------------------------------------
-$ git checkout mybranch
-$ echo "Work, work, work" >>hello
-$ git commit -m 'Some work.' hello
-------------------------------------------------
+-------------------------------------
+git diff HEAD^ HEAD
+-------------------------------------
-Here, we just added another line to `hello`, and we used a shorthand for
-doing both `git-update-index hello` and `git commit` by just giving the
-filename directly to `git commit`. The `-m` flag is to give the
-commit log message from the command line.
+shows the difference between the most-recently checked-in state of
+the tree and the previous state, and
-Now, to make it a bit more interesting, let's assume that somebody else
-does some work in the original branch, and simulate that by going back
-to the master branch, and editing the same file differently there:
+-------------------------------------
+git diff HEAD^^ HEAD^
+-------------------------------------
-------------
-$ git checkout master
-------------
+shows the difference between that previous state and the state two
+commits ago. Also, HEAD~5 can be used as a shorthand for HEAD^^^^^,
+and more generally HEAD~n can refer to the nth previous commit.
+Commits representing merges have more than one parent, and you can
+specify which parent to follow in that case; see
+gitlink:git-rev-parse[1].
-Here, take a moment to look at the contents of `hello`, and notice how they
-don't contain the work we just did in `mybranch` -- because that work
-hasn't happened in the `master` branch at all. Then do
-
-------------
-$ echo "Play, play, play" >>hello
-$ echo "Lots of fun" >>example
-$ git commit -m 'Some fun.' hello example
-------------
-
-since the master branch is obviously in a much better mood.
-
-Now, you've got two branches, and you decide that you want to merge the
-work done. Before we do that, let's introduce a cool graphical tool that
-helps you view what's going on:
-
-----------------
-$ gitk --all
-----------------
-
-will show you graphically both of your branches (that's what the `\--all`
-means: normally it will just show you your current `HEAD`) and their
-histories. You can also see exactly how they came to be from a common
-source.
-
-Anyway, let's exit `gitk` (`^Q` or the File menu), and decide that we want
-to merge the work we did on the `mybranch` branch into the `master`
-branch (which is currently our `HEAD` too). To do that, there's a nice
-script called `git merge`, which wants to know which branches you want
-to resolve and what the merge is all about:
-
-------------
-$ git merge "Merge work in mybranch" HEAD mybranch
-------------
-
-where the first argument is going to be used as the commit message if
-the merge can be resolved automatically.
-
-Now, in this case we've intentionally created a situation where the
-merge will need to be fixed up by hand, though, so git will do as much
-of it as it can automatically (which in this case is just merge the `example`
-file, which had no differences in the `mybranch` branch), and say:
-
-----------------
- Trying really trivial in-index merge...
- fatal: Merge requires file-level merging
- Nope.
- ...
- Auto-merging hello
- CONFLICT (content): Merge conflict in hello
- Automatic merge failed/prevented; fix up by hand
-----------------
-
-which is way too verbose, but it basically tells you that it failed the
-really trivial merge ("Simple merge") and did an "Automatic merge"
-instead, but that too failed due to conflicts in `hello`.
-
-Not to worry. It left the (trivial) conflict in `hello` in the same form you
-should already be well used to if you've ever used CVS, so let's just
-open `hello` in our editor (whatever that may be), and fix it up somehow.
-I'd suggest just making it so that `hello` contains all four lines:
-
-------------
-Hello World
-It's a new day for git
-Play, play, play
-Work, work, work
-------------
-
-and once you're happy with your manual merge, just do a
-
-------------
-$ git commit hello
-------------
-
-which will very loudly warn you that you're now committing a merge
-(which is correct, so never mind), and you can write a small merge
-message about your adventures in git-merge-land.
-
-After you're done, start up `gitk \--all` to see graphically what the
-history looks like. Notice that `mybranch` still exists, and you can
-switch to it, and continue to work with it if you want to. The
-`mybranch` branch will not contain the merge, but next time you merge it
-from the `master` branch, git will know how you merged it, so you'll not
-have to do _that_ merge again.
-
-Another useful tool, especially if you do not always work in X-Window
-environment, is `git show-branch`.
-
-------------------------------------------------
-$ git show-branch master mybranch
-* [master] Merge work in mybranch
- ! [mybranch] Some work.
---
-- [master] Merge work in mybranch
-*+ [mybranch] Some work.
-------------------------------------------------
-
-The first two lines indicate that it is showing the two branches
-and the first line of the commit log message from their
-top-of-the-tree commits, you are currently on `master` branch
-(notice the asterisk `*` character), and the first column for
-the later output lines is used to show commits contained in the
-`master` branch, and the second column for the `mybranch`
-branch. Three commits are shown along with their log messages.
-All of them have non blank characters in the first column (`*`
-shows an ordinary commit on the current branch, `.` is a merge commit), which
-means they are now part of the `master` branch. Only the "Some
-work" commit has the plus `+` character in the second column,
-because `mybranch` has not been merged to incorporate these
-commits from the master branch. The string inside brackets
-before the commit log message is a short name you can use to
-name the commit. In the above example, 'master' and 'mybranch'
-are branch heads. 'master~1' is the first parent of 'master'
-branch head. Please see 'git-rev-parse' documentation if you
-see more complex cases.
-
-Now, let's pretend you are the one who did all the work in
-`mybranch`, and the fruit of your hard work has finally been merged
-to the `master` branch. Let's go back to `mybranch`, and run
-resolve to get the "upstream changes" back to your branch.
-
-------------
-$ git checkout mybranch
-$ git merge "Merge upstream changes." HEAD master
-------------
-
-This outputs something like this (the actual commit object names
-would be different)
-
-----------------
-Updating from ae3a2da... to a80b4aa....
- example | 1 +
- hello | 1 +
- 2 files changed, 2 insertions(+), 0 deletions(-)
-----------------
-
-Because your branch did not contain anything more than what are
-already merged into the `master` branch, the resolve operation did
-not actually do a merge. Instead, it just updated the top of
-the tree of your branch to that of the `master` branch. This is
-often called 'fast forward' merge.
-
-You can run `gitk \--all` again to see how the commit ancestry
-looks like, or run `show-branch`, which tells you this.
-
-------------------------------------------------
-$ git show-branch master mybranch
-! [master] Merge work in mybranch
- * [mybranch] Merge work in mybranch
---
--- [master] Merge work in mybranch
-------------------------------------------------
-
-
-Merging external work
----------------------
-
-It's usually much more common that you merge with somebody else than
-merging with your own branches, so it's worth pointing out that git
-makes that very easy too, and in fact, it's not that different from
-doing a `git merge`. In fact, a remote merge ends up being nothing
-more than "fetch the work from a remote repository into a temporary tag"
-followed by a `git merge`.
-
-Fetching from a remote repository is done by, unsurprisingly,
-`git fetch`:
-
-----------------
-$ git fetch <remote-repository>
-----------------
-
-One of the following transports can be used to name the
-repository to download from:
-
-Rsync::
- `rsync://remote.machine/path/to/repo.git/`
-+
-Rsync transport is usable for both uploading and downloading,
-but is completely unaware of what git does, and can produce
-unexpected results when you download from the public repository
-while the repository owner is uploading into it via `rsync`
-transport. Most notably, it could update the files under
-`refs/` which holds the object name of the topmost commits
-before uploading the files in `objects/` -- the downloader would
-obtain head commit object name while that object itself is still
-not available in the repository. For this reason, it is
-considered deprecated.
-
-SSH::
- `remote.machine:/path/to/repo.git/` or
-+
-`ssh://remote.machine/path/to/repo.git/`
-+
-This transport can be used for both uploading and downloading,
-and requires you to have a log-in privilege over `ssh` to the
-remote machine. It finds out the set of objects the other side
-lacks by exchanging the head commits both ends have and
-transfers (close to) minimum set of objects. It is by far the
-most efficient way to exchange git objects between repositories.
-
-Local directory::
- `/path/to/repo.git/`
-+
-This transport is the same as SSH transport but uses `sh` to run
-both ends on the local machine instead of running other end on
-the remote machine via `ssh`.
-
-git Native::
- `git://remote.machine/path/to/repo.git/`
-+
-This transport was designed for anonymous downloading. Like SSH
-transport, it finds out the set of objects the downstream side
-lacks and transfers (close to) minimum set of objects.
-
-HTTP(S)::
- `http://remote.machine/path/to/repo.git/`
-+
-Downloader from http and https URL
-first obtains the topmost commit object name from the remote site
-by looking at the specified refname under `repo.git/refs/` directory,
-and then tries to obtain the
-commit object by downloading from `repo.git/objects/xx/xxx\...`
-using the object name of that commit object. Then it reads the
-commit object to find out its parent commits and the associate
-tree object; it repeats this process until it gets all the
-necessary objects. Because of this behaviour, they are
-sometimes also called 'commit walkers'.
-+
-The 'commit walkers' are sometimes also called 'dumb
-transports', because they do not require any git aware smart
-server like git Native transport does. Any stock HTTP server
-that does not even support directory index would suffice. But
-you must prepare your repository with `git-update-server-info`
-to help dumb transport downloaders.
-+
-There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
-programs, which are 'commit walkers'; they outlived their
-usefulness when git Native and SSH transports were introduced,
-and not used by `git pull` or `git push` scripts.
-
-Once you fetch from the remote repository, you `resolve` that
-with your current branch.
-
-However -- it's such a common thing to `fetch` and then
-immediately `resolve`, that it's called `git pull`, and you can
-simply do
-
-----------------
-$ git pull <remote-repository>
-----------------
-
-and optionally give a branch-name for the remote end as a second
-argument.
-
-[NOTE]
-You could do without using any branches at all, by
-keeping as many local repositories as you would like to have
-branches, and merging between them with `git pull`, just like
-you merge between branches. The advantage of this approach is
-that it lets you keep set of files for each `branch` checked
-out and you may find it easier to switch back and forth if you
-juggle multiple lines of development simultaneously. Of
-course, you will pay the price of more disk usage to hold
-multiple working trees, but disk space is cheap these days.
-
-[NOTE]
-You could even pull from your own repository by
-giving '.' as <remote-repository> parameter to `git pull`. This
-is useful when you want to merge a local branch (or more, if you
-are making an Octopus) into the current branch.
-
-It is likely that you will be pulling from the same remote
-repository from time to time. As a short hand, you can store
-the remote repository URL in a file under .git/remotes/
-directory, like this:
-
-------------------------------------------------
-$ mkdir -p .git/remotes/
-$ cat >.git/remotes/linus <<\EOF
-URL: http://www.kernel.org/pub/scm/git/git.git/
-EOF
-------------------------------------------------
-
-and use the filename to `git pull` instead of the full URL.
-The URL specified in such file can even be a prefix
-of a full URL, like this:
-
-------------------------------------------------
-$ cat >.git/remotes/jgarzik <<\EOF
-URL: http://www.kernel.org/pub/scm/linux/git/jgarzik/
-EOF
-------------------------------------------------
-
-
-Examples.
-
-. `git pull linus`
-. `git pull linus tag v0.99.1`
-. `git pull jgarzik/netdev-2.6.git/ e100`
-
-the above are equivalent to:
-
-. `git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD`
-. `git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1`
-. `git pull http://www.kernel.org/pub/.../jgarzik/netdev-2.6.git e100`
+The name of a branch can also be used to refer to the most recent
+commit on that branch; so you can also say things like
+-------------------------------------
+git diff HEAD experimental
+-------------------------------------
-How does the merge work?
-------------------------
+to see the difference between the most-recently committed tree in
+the current branch and the most-recently committed tree in the
+experimental branch.
-We said this tutorial shows what plumbing does to help you cope
-with the porcelain that isn't flushing, but we so far did not
-talk about how the merge really works. If you are following
-this tutorial the first time, I'd suggest to skip to "Publishing
-your work" section and come back here later.
-
-OK, still with me? To give us an example to look at, let's go
-back to the earlier repository with "hello" and "example" file,
-and bring ourselves back to the pre-merge state:
-
-------------
-$ git show-branch --more=3 master mybranch
-! [master] Merge work in mybranch
- * [mybranch] Merge work in mybranch
---
--- [master] Merge work in mybranch
-+* [master^2] Some work.
-+* [master^] Some fun.
-------------
-
-Remember, before running `git merge`, our `master` head was at
-"Some fun." commit, while our `mybranch` head was at "Some
-work." commit.
-
-------------
-$ git checkout mybranch
-$ git reset --hard master^2
-$ git checkout master
-$ git reset --hard master^
-------------
+But you may find it more useful to see the list of commits made in
+the experimental branch but not in the current branch, and
-After rewinding, the commit structure should look like this:
+-------------------------------------
+git whatchanged HEAD..experimental
+-------------------------------------
-------------
-$ git show-branch
-* [master] Some fun.
- ! [mybranch] Some work.
---
- + [mybranch] Some work.
-* [master] Some fun.
-*+ [mybranch^] New day.
-------------
-
-Now we are ready to experiment with the merge by hand.
-
-`git merge` command, when merging two branches, uses 3-way merge
-algorithm. First, it finds the common ancestor between them.
-The command it uses is `git-merge-base`:
-
-------------
-$ mb=$(git-merge-base HEAD mybranch)
-------------
-
-The command writes the commit object name of the common ancestor
-to the standard output, so we captured its output to a variable,
-because we will be using it in the next step. BTW, the common
-ancestor commit is the "New day." commit in this case. You can
-tell it by:
-
-------------
-$ git-name-rev $mb
-my-first-tag
-------------
-
-After finding out a common ancestor commit, the second step is
-this:
-
-------------
-$ git-read-tree -m -u $mb HEAD mybranch
-------------
-
-This is the same `git-read-tree` command we have already seen,
-but it takes three trees, unlike previous examples. This reads
-the contents of each tree into different 'stage' in the index
-file (the first tree goes to stage 1, the second stage 2,
-etc.). After reading three trees into three stages, the paths
-that are the same in all three stages are 'collapsed' into stage
-0. Also paths that are the same in two of three stages are
-collapsed into stage 0, taking the SHA1 from either stage 2 or
-stage 3, whichever is different from stage 1 (i.e. only one side
-changed from the common ancestor).
-
-After 'collapsing' operation, paths that are different in three
-trees are left in non-zero stages. At this point, you can
-inspect the index file with this command:
-
-------------
-$ git-ls-files --stage
-100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
-100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
-------------
-
-In our example of only two files, we did not have unchanged
-files so only 'example' resulted in collapsing, but in real-life
-large projects, only small number of files change in one commit,
-and this 'collapsing' tends to trivially merge most of the paths
-fairly quickly, leaving only a handful the real changes in non-zero
-stages.
-
-To look at only non-zero stages, use `\--unmerged` flag:
-
-------------
-$ git-ls-files --unmerged
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
-100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
-------------
-
-The next step of merging is to merge these three versions of the
-file, using 3-way merge. This is done by giving
-`git-merge-one-file` command as one of the arguments to
-`git-merge-index` command:
-
-------------
-$ git-merge-index git-merge-one-file hello
-Auto-merging hello.
-merge: warning: conflicts during merge
-ERROR: Merge conflict in hello.
-fatal: merge program failed
-------------
-
-`git-merge-one-file` script is called with parameters to
-describe those three versions, and is responsible to leave the
-merge results in the working tree.
-It is a fairly straightforward shell script, and
-eventually calls `merge` program from RCS suite to perform a
-file-level 3-way merge. In this case, `merge` detects
-conflicts, and the merge result with conflict marks is left in
-the working tree.. This can be seen if you run `ls-files
---stage` again at this point:
-
-------------
-$ git-ls-files --stage
-100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
-100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
-------------
-
-This is the state of the index file and the working file after
-`git merge` returns control back to you, leaving the conflicting
-merge for you to resolve. Notice that the path `hello` is still
-unmerged, and what you see with `git diff` at this point is
-differences since stage 2 (i.e. your version).
-
-
-Publishing your work
---------------------
-
-So we can use somebody else's work from a remote repository; but
-how can *you* prepare a repository to let other people pull from
-it?
-
-Your do your real work in your working tree that has your
-primary repository hanging under it as its `.git` subdirectory.
-You *could* make that repository accessible remotely and ask
-people to pull from it, but in practice that is not the way
-things are usually done. A recommended way is to have a public
-repository, make it reachable by other people, and when the
-changes you made in your primary working tree are in good shape,
-update the public repository from it. This is often called
-'pushing'.
-
-[NOTE]
-This public repository could further be mirrored, and that is
-how git repositories at `kernel.org` are managed.
-
-Publishing the changes from your local (private) repository to
-your remote (public) repository requires a write privilege on
-the remote machine. You need to have an SSH account there to
-run a single command, `git-receive-pack`.
-
-First, you need to create an empty repository on the remote
-machine that will house your public repository. This empty
-repository will be populated and be kept up-to-date by pushing
-into it later. Obviously, this repository creation needs to be
-done only once.
-
-[NOTE]
-`git push` uses a pair of programs,
-`git-send-pack` on your local machine, and `git-receive-pack`
-on the remote machine. The communication between the two over
-the network internally uses an SSH connection.
-
-Your private repository's git directory is usually `.git`, but
-your public repository is often named after the project name,
-i.e. `<project>.git`. Let's create such a public repository for
-project `my-git`. After logging into the remote machine, create
-an empty directory:
-
-------------
-$ mkdir my-git.git
-------------
-
-Then, make that directory into a git repository by running
-`git init-db`, but this time, since its name is not the usual
-`.git`, we do things slightly differently:
-
-------------
-$ GIT_DIR=my-git.git git-init-db
-------------
-
-Make sure this directory is available for others you want your
-changes to be pulled by via the transport of your choice. Also
-you need to make sure that you have the `git-receive-pack`
-program on the `$PATH`.
-
-[NOTE]
-Many installations of sshd do not invoke your shell as the login
-shell when you directly run programs; what this means is that if
-your login shell is `bash`, only `.bashrc` is read and not
-`.bash_profile`. As a workaround, make sure `.bashrc` sets up
-`$PATH` so that you can run `git-receive-pack` program.
-
-[NOTE]
-If you plan to publish this repository to be accessed over http,
-you should do `chmod +x my-git.git/hooks/post-update` at this
-point. This makes sure that every time you push into this
-repository, `git-update-server-info` is run.
-
-Your "public repository" is now ready to accept your changes.
-Come back to the machine you have your private repository. From
-there, run this command:
-
-------------
-$ git push <public-host>:/path/to/my-git.git master
-------------
-
-This synchronizes your public repository to match the named
-branch head (i.e. `master` in this case) and objects reachable
-from them in your current repository.
-
-As a real example, this is how I update my public git
-repository. Kernel.org mirror network takes care of the
-propagation to other publicly visible machines:
-
-------------
-$ git push master.kernel.org:/pub/scm/git/git.git/
-------------
+will do that, just as
+-------------------------------------
+git whatchanged experimental..HEAD
+-------------------------------------
-Packing your repository
------------------------
+will show the list of commits made on the HEAD but not included in
+experimental.
+
+You can also give commits convenient names of your own: after running
+
+-------------------------------------
+$ git-tag v2.5 HEAD^^
+-------------------------------------
-Earlier, we saw that one file under `.git/objects/??/` directory
-is stored for each git object you create. This representation
-is efficient to create atomically and safely, but
-not so convenient to transport over the network. Since git objects are
-immutable once they are created, there is a way to optimize the
-storage by "packing them together". The command
-
-------------
-$ git repack
-------------
-
-will do it for you. If you followed the tutorial examples, you
-would have accumulated about 17 objects in `.git/objects/??/`
-directories by now. `git repack` tells you how many objects it
-packed, and stores the packed file in `.git/objects/pack`
-directory.
-
-[NOTE]
-You will see two files, `pack-\*.pack` and `pack-\*.idx`,
-in `.git/objects/pack` directory. They are closely related to
-each other, and if you ever copy them by hand to a different
-repository for whatever reason, you should make sure you copy
-them together. The former holds all the data from the objects
-in the pack, and the latter holds the index for random
-access.
-
-If you are paranoid, running `git-verify-pack` command would
-detect if you have a corrupt pack, but do not worry too much.
-Our programs are always perfect ;-).
-
-Once you have packed objects, you do not need to leave the
-unpacked objects that are contained in the pack file anymore.
-
-------------
-$ git prune-packed
-------------
-
-would remove them for you.
-
-You can try running `find .git/objects -type f` before and after
-you run `git prune-packed` if you are curious. Also `git
-count-objects` would tell you how many unpacked objects are in
-your repository and how much space they are consuming.
-
-[NOTE]
-`git pull` is slightly cumbersome for HTTP transport, as a
-packed repository may contain relatively few objects in a
-relatively large pack. If you expect many HTTP pulls from your
-public repository you might want to repack & prune often, or
-never.
-
-If you run `git repack` again at this point, it will say
-"Nothing to pack". Once you continue your development and
-accumulate the changes, running `git repack` again will create a
-new pack, that contains objects created since you packed your
-repository the last time. We recommend that you pack your project
-soon after the initial import (unless you are starting your
-project from scratch), and then run `git repack` every once in a
-while, depending on how active your project is.
-
-When a repository is synchronized via `git push` and `git pull`
-objects packed in the source repository are usually stored
-unpacked in the destination, unless rsync transport is used.
-While this allows you to use different packing strategies on
-both ends, it also means you may need to repack both
-repositories every once in a while.
-
-
-Working with Others
--------------------
-
-Although git is a truly distributed system, it is often
-convenient to organize your project with an informal hierarchy
-of developers. Linux kernel development is run this way. There
-is a nice illustration (page 17, "Merges to Mainline") in Randy
-Dunlap's presentation (`http://tinyurl.com/a2jdg`).
-
-It should be stressed that this hierarchy is purely *informal*.
-There is nothing fundamental in git that enforces the "chain of
-patch flow" this hierarchy implies. You do not have to pull
-from only one remote repository.
-
-A recommended workflow for a "project lead" goes like this:
-
-1. Prepare your primary repository on your local machine. Your
- work is done there.
-
-2. Prepare a public repository accessible to others.
-+
-If other people are pulling from your repository over dumb
-transport protocols (HTTP), you need to keep this repository
-'dumb transport friendly'. After `git init-db`,
-`$GIT_DIR/hooks/post-update` copied from the standard templates
-would contain a call to `git-update-server-info` but the
-`post-update` hook itself is disabled by default -- enable it
-with `chmod +x post-update`. This makes sure `git-update-server-info`
-keeps the necessary files up-to-date.
-
-3. Push into the public repository from your primary
- repository.
-
-4. `git repack` the public repository. This establishes a big
- pack that contains the initial set of objects as the
- baseline, and possibly `git prune` if the transport
- used for pulling from your repository supports packed
- repositories.
-
-5. Keep working in your primary repository. Your changes
- include modifications of your own, patches you receive via
- e-mails, and merges resulting from pulling the "public"
- repositories of your "subsystem maintainers".
-+
-You can repack this private repository whenever you feel like.
-
-6. Push your changes to the public repository, and announce it
- to the public.
-
-7. Every once in a while, "git repack" the public repository.
- Go back to step 5. and continue working.
-
-
-A recommended work cycle for a "subsystem maintainer" who works
-on that project and has an own "public repository" goes like this:
-
-1. Prepare your work repository, by `git clone` the public
- repository of the "project lead". The URL used for the
- initial cloning is stored in `.git/remotes/origin`.
-
-2. Prepare a public repository accessible to others, just like
- the "project lead" person does.
-
-3. Copy over the packed files from "project lead" public
- repository to your public repository, unless the "project
- lead" repository lives on the same machine as yours. In the
- latter case, you can use `objects/info/alternates` file to
- point at the repository you are borrowing from.
-
-4. Push into the public repository from your primary
- repository. Run `git repack`, and possibly `git prune` if the
- transport used for pulling from your repository supports
- packed repositories.
-
-5. Keep working in your primary repository. Your changes
- include modifications of your own, patches you receive via
- e-mails, and merges resulting from pulling the "public"
- repositories of your "project lead" and possibly your
- "sub-subsystem maintainers".
-+
-You can repack this private repository whenever you feel
-like.
-
-6. Push your changes to your public repository, and ask your
- "project lead" and possibly your "sub-subsystem
- maintainers" to pull from it.
-
-7. Every once in a while, `git repack` the public repository.
- Go back to step 5. and continue working.
-
-
-A recommended work cycle for an "individual developer" who does
-not have a "public" repository is somewhat different. It goes
-like this:
-
-1. Prepare your work repository, by `git clone` the public
- repository of the "project lead" (or a "subsystem
- maintainer", if you work on a subsystem). The URL used for
- the initial cloning is stored in `.git/remotes/origin`.
-
-2. Do your work in your repository on 'master' branch.
-
-3. Run `git fetch origin` from the public repository of your
- upstream every once in a while. This does only the first
- half of `git pull` but does not merge. The head of the
- public repository is stored in `.git/refs/heads/origin`.
-
-4. Use `git cherry origin` to see which ones of your patches
- were accepted, and/or use `git rebase origin` to port your
- unmerged changes forward to the updated upstream.
-
-5. Use `git format-patch origin` to prepare patches for e-mail
- submission to your upstream and send it out. Go back to
- step 2. and continue.
-
-
-Working with Others, Shared Repository Style
---------------------------------------------
-
-If you are coming from CVS background, the style of cooperation
-suggested in the previous section may be new to you. You do not
-have to worry. git supports "shared public repository" style of
-cooperation you are probably more familiar with as well.
-
-For this, set up a public repository on a machine that is
-reachable via SSH by people with "commit privileges". Put the
-committers in the same user group and make the repository
-writable by that group. Make sure their umasks are set up to
-allow group members to write into directories other members
-have created.
-
-You, as an individual committer, then:
-
-- First clone the shared repository to a local repository:
-------------------------------------------------
-$ git clone repo.shared.xz:/pub/scm/project.git/ my-project
-$ cd my-project
-$ hack away
-------------------------------------------------
-
-- Merge the work others might have done while you were hacking
- away:
-------------------------------------------------
-$ git pull origin
-$ test the merge result
-------------------------------------------------
-[NOTE]
-================================
-The first `git clone` would have placed the following in
-`my-project/.git/remotes/origin` file, and that's why this and
-the next step work.
-------------
-URL: repo.shared.xz:/pub/scm/project.git/ my-project
-Pull: master:origin
-------------
-================================
-
-- push your work as the new head of the shared
- repository.
-------------------------------------------------
-$ git push origin master
-------------------------------------------------
-If somebody else pushed into the same shared repository while
-you were working locally, `git push` in the last step would
-complain, telling you that the remote `master` head does not
-fast forward. You need to pull and merge those other changes
-back before you push your work when it happens.
-
-
-Advanced Shared Repository Management
--------------------------------------
-
-Being able to push into a shared repository means being able to
-write into it. If your developers are coming over the network,
-this means you, as the repository administrator, need to give
-each of them an SSH access to the shared repository machine.
-
-In some cases, though, you may not want to give a normal shell
-account to them, but want to restrict them to be able to only
-do `git push` into the repository and nothing else.
-
-You can achieve this by setting the login shell of your
-developers on the shared repository host to `git-shell` program.
-
-[NOTE]
-Most likely you would also need to list `git-shell` program in
-`/etc/shells` file.
-
-This restricts the set of commands that can be run from incoming
-SSH connection for these users to only `receive-pack` and
-`upload-pack`, so the only thing they can do are `git fetch` and
-`git push`.
-
-You still need to create UNIX user accounts for each developer,
-and put them in the same group. Make sure that the repository
-shared among these developers is writable by that group.
-
-. Initializing the shared repository with `git-init-db --shared`
-helps somewhat.
-
-. Run the following in the shared repository:
-+
-------------
-$ chgrp -R $group repo.git
-$ find repo.git -type d -print | xargs chmod ug+rwx,g+s
-$ GIT_DIR=repo.git git repo-config core.sharedrepository true
-------------
-
-The above measures make sure that directories lazily created in
-`$GIT_DIR` are writable by group members. You, as the
-repository administrator, are still responsible to make sure
-your developers belong to that shared repository group and set
-their umask to a value no stricter than 027 (i.e. at least allow
-reading and searching by group members).
-
-You can implement finer grained branch policies using update
-hooks. There is a document ("control access to branches") in
-Documentation/howto by Carl Baldwin and JC outlining how to (1)
-limit access to branch per user, (2) forbid overwriting existing
-tags.
+you can refer to HEAD^^ by the name "v2.5". If you intend to share
+this name with other people (for example, to identify a release
+version), you should create a "tag" object, and perhaps sign it; see
+gitlink:git-tag[1] for details.
+You can revisit the old state of a tree, and make further
+modifications if you wish, using git branch: the command
-Bundling your work together
----------------------------
+-------------------------------------
+$ git branch stable-release v2.5
+-------------------------------------
+
+will create a new branch named "stable-release" starting from the
+commit which you tagged with the name v2.5.
-It is likely that you will be working on more than one thing at
-a time. It is easy to manage those more-or-less independent tasks
-using branches with git.
-
-We have already seen how branches work previously,
-with "fun and work" example using two branches. The idea is the
-same if there are more than two branches. Let's say you started
-out from "master" head, and have some new code in the "master"
-branch, and two independent fixes in the "commit-fix" and
-"diff-fix" branches:
-
-------------
-$ git show-branch
-! [commit-fix] Fix commit message normalization.
- ! [diff-fix] Fix rename detection.
- * [master] Release candidate #1
----
- + [diff-fix] Fix rename detection.
- + [diff-fix~1] Better common substring algorithm.
-+ [commit-fix] Fix commit message normalization.
- * [master] Release candidate #1
-++* [diff-fix~2] Pretty-print messages.
-------------
-
-Both fixes are tested well, and at this point, you want to merge
-in both of them. You could merge in 'diff-fix' first and then
-'commit-fix' next, like this:
-
-------------
-$ git merge 'Merge fix in diff-fix' master diff-fix
-$ git merge 'Merge fix in commit-fix' master commit-fix
-------------
-
-Which would result in:
-
-------------
-$ git show-branch
-! [commit-fix] Fix commit message normalization.
- ! [diff-fix] Fix rename detection.
- * [master] Merge fix in commit-fix
----
- - [master] Merge fix in commit-fix
-+ * [commit-fix] Fix commit message normalization.
- - [master~1] Merge fix in diff-fix
- +* [diff-fix] Fix rename detection.
- +* [diff-fix~1] Better common substring algorithm.
- * [master~2] Release candidate #1
-++* [master~3] Pretty-print messages.
-------------
-
-However, there is no particular reason to merge in one branch
-first and the other next, when what you have are a set of truly
-independent changes (if the order mattered, then they are not
-independent by definition). You could instead merge those two
-branches into the current branch at once. First let's undo what
-we just did and start over. We would want to get the master
-branch before these two merges by resetting it to 'master~2':
-
-------------
-$ git reset --hard master~2
-------------
-
-You can make sure 'git show-branch' matches the state before
-those two 'git merge' you just did. Then, instead of running
-two 'git merge' commands in a row, you would pull these two
-branch heads (this is known as 'making an Octopus'):
-
-------------
-$ git pull . commit-fix diff-fix
-$ git show-branch
-! [commit-fix] Fix commit message normalization.
- ! [diff-fix] Fix rename detection.
- * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
----
- - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
-+ * [commit-fix] Fix commit message normalization.
- +* [diff-fix] Fix rename detection.
- +* [diff-fix~1] Better common substring algorithm.
- * [master~1] Release candidate #1
-++* [master~2] Pretty-print messages.
-------------
-
-Note that you should not do Octopus because you can. An octopus
-is a valid thing to do and often makes it easier to view the
-commit history if you are pulling more than two independent
-changes at the same time. However, if you have merge conflicts
-with any of the branches you are merging in and need to hand
-resolve, that is an indication that the development happened in
-those branches were not independent after all, and you should
-merge two at a time, documenting how you resolved the conflicts,
-and the reason why you preferred changes made in one side over
-the other. Otherwise it would make the project history harder
-to follow, not easier.
+You can reset the state of any branch to an earlier commit at any
+time with
+
+-------------------------------------
+$ git reset --hard v2.5
+-------------------------------------
-[ to be continued.. cvsimports ]
+This will remove all later commits from this branch and reset the
+working tree to the state it had when the given commit was made. If
+this branch is the only branch containing the later commits, those
+later changes will be lost. Don't use "git reset" on a
+publicly-visible branch that other developers pull from, as git will
+be confused by history that disappears in this way.
+
+Next Steps
+----------
+
+Some good commands to explore next:
+
+ * gitlink:git-diff[1]: This flexible command does much more than
+ we've seen in the few examples above.
+
+ * gitlink:git-format-patch[1], gitlink:git-am[1]: These convert
+ series of git commits into emailed patches, and vice versa,
+ useful for projects such as the linux kernel which rely heavily
+ on emailed patches.
+
+ * gitlink:git-bisect[1]: When there is a regression in your
+ project, one way to track down the bug is by searching through
+ the history to find the exact commit that's to blame. Git bisect
+ can help you perform a binary search for that commit. It is
+ smart enough to perform a close-to-optimal search even in the
+ case of complex non-linear history with lots of merged branches.
+
+Other good starting points include link:everyday.html[Everday GIT
+with 20 Commands Or So] and link:cvs-migration.html[git for CVS
+users]. Also, link:core-tutorial.html[A short git tutorial] gives an
+introduction to lower-level git commands for advanced users and
+developers.
--
0.99.8b-g58e3
^ permalink raw reply related
* Re: Notes on Subproject Support
From: Junio C Hamano @ 2006-01-23 4:36 UTC (permalink / raw)
To: Daniel Barkalow; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0601222104120.25300@iabervon.org>
Daniel Barkalow <barkalow@iabervon.org> writes:
>> tree 04803b09c300c8325258ccf2744115acc4c57067
>
> Does this tree include trees for the bound projects?
Yes, this part has not been changed from earlier thoughts.
>> bind 5b2bcc7b2d546c636f79490655b3347acc91d17f linux-2.6/
>> bind 0bdd79af62e8621359af08f0afca0ce977348ac7 appliance/
>> author Junio C Hamano <junio@kernel.org> 1137965565 -0800
>> committer Junio C Hamano <junio@kernel.org> 1137965565 -0800
The tree 04803b... tree has everything. If you run git-ls-tree
on 04803b... would have a tree object recorded at linux-2.6, and
it is the same as the tree associated with the commit 5b2bcc...
> I think "cd linux-2.6 && git commit" should work for the subproject, too,
> but that can be a later enhancement.
It's just a matter of Porcelain scripting, so that is probably
true. However I do not want people to get too used to it and
expect "cd Documentation && git commit" to work in git.git
repository.
>> With the new `--subproject` option, the directory structure
>> rooted at `linux-2.6/` part is written out as a tree, and a new
>> commit object that records that tree object with the commit
>> bound to that portion of the tree (`5b2bcc7b` in the above
>> example) as its parent is created.
>
> And the commit is written to the index, in the special slot for the
> subproject, replacing its parent, I assume.
Yes. It would probably be done with `update-index --bind` to
update the bound subproject commit there.
>> Switching branches
>> ------------------
>>
>> Along with the traditional two-way merge by `read-tree -m -u`,
>> we would need to look at:
>>
>> . `bind` lines in the current `HEAD` commit.
>>
>> . `bind` lines in the commit we are switching to.
>>
>> . subproject binding information in the index file.
>>
>> to make sure we do sensible things.
>
> This is one place I think storing the bindings in the commit is awkward.
> read-tree deals in trees (hence the name), but will need information from
> the commit.
That's why it is 'along with'. Dealing with binding information
can be done between commits and index without bothering tree
objects. read-tree would not have to deal with it, and I think
keeping it that way is probably a good idea.
In other words, I think the design so far does not require us to
touch tree objects at all, and I'd be happy if we do not have to.
One reason I started the bound commit approach was exactly
because I only needed to muck with commit objects and did not
have to touch trees and blobs; after trying to implement the
core level for "gitlink", which I ended up touching quite a lot
and have abandoned for now.
Here is an update to the still-WIP draft.
-- >8 --
Separate role of read-tree and update-index cleaner
The previous draft prematurely merged what read-tree --prefix
does with what update-index --bind would do. Keep them separate
for now until we know what the common patterns would be.
Introduce 'update-index --unbind'. We would probably need a new
command that extracts bind information out of index when we
start writing Porcelainish, but it is not specified yet.
Attempt to clarify what the "merging into subproject part" would
do a bit. "git pull --subproject=" is fetch + merge, just like
the current subproject-unaware 'git pull' is.
---
diff --git a/Subpro.txt b/Subpro.txt
index 4036e71..837cab8 100644
--- a/Subpro.txt
+++ b/Subpro.txt
@@ -95,19 +95,22 @@ $ git bind-projects \
$gadget_commit appliance/
------------
-This would do an equivalent of:
+This would probably do an equivalent of:
------------
+$ rm -f "$GIT_DIR/index"
$ git read-tree --prefix=linux-2.6/ $kernel_commit
$ git read-tree --prefix=appliance/ $gadget_commit
+$ git update-index --bind linux-2.6/ $kernel_commit
+$ git update-index --bind appliance/ $gadget_commit
------------
[NOTE]
============
Earlier outlines sent to the git mailing list talked
about `$GIT_DIR/bind` to record what subproject are bound to
-which subtree in the curent working tree and index. This
+which subtree in the current working tree and index. This
proposal instead records that information in the index file
-when `--prefix=linux-2.6/` is given to `read-tree`.
+with `update-index --bind` command.
Also note that in this round of proposal, there is no separate
branches that keep track of heads of subprojects.
@@ -258,9 +261,11 @@ our branch, and redoing the merge (this
anyway). It might go like this:
------------
-$ git bind-projects \
- $kernel_commit kernel/ \
- $gadget_commit gadget/
+$ git reset
+$ git update-index --unbind linux-2.6/
+$ git update-index --unbind appliance/
+$ git update-index --bind $kernel_commit kernel/
+$ git update-index --bind $gadget_commit gadget/
$ git commit -m 'Prepare for merge with side branch'
$ git merge 'Merge in a side branch' HEAD side
error: the merged heads have subprojects bound at different places.
@@ -336,7 +341,55 @@ make a commit.
[NOTE]
This suggests that we would need to have something similar to
-`MERGE_HEAD` for merging the subproject part.
+`MERGE_HEAD` for merging the subproject part. In the case of
+merging two toplevel project commits, we probably can read the
+`bind` lines from the `MERGE_HEAD` commit and either our `HEAD`
+commit or our index file. Further, we probably would require
+that the latter two must match, just as we currently require the
+index file matches our `HEAD` commit before `git merge`.
+Just like the current `pull = fetch + merge` semantics, the
+subproject aware version `git pull \--subproject=frotz` would be
+a `git fetch \--subproject=frotz` followed by a `git merge
+\--subproject=frotz`. So the above would be:
+. Fetch the head.
++
+------------
+$ git fetch --subproject=kernel/ git://git.kernel.org/.../linux-2.6/
+------------
++
+which would do:
+. fetch the commit chain from the remote repository.
+. write something like this to `FETCH_HEAD`:
++
+------------
+3ee68c4...\tfor-merge-into kernel/\tbranch 'master' of git://.../linux-2.6
+------------
+
+. Run `git merge`.
++
+------------
+$ git merge --subproject=kernel/ \
+ 'Merge git://.../linux-2.6 into kernel/' HEAD 3ee68c4...
+------------
+
+. In case it does not cleanly automerge, `git merge` would write
+the necessary information for a later `git commit` to use in
+`MERGE_HEAD`. It may look like this:
++
+------------
+3ee68c4af3fd7228c1be63254b9f884614f9ebb2 kernel/
+------------
+
+With this, a later invocation of `git commit` to record the
+result of hand resolving would be able to notice that:
+
+. We should be first resolving `kernel/` subproject.
+. The remote `HEAD` is `3ee68c4...` commit.
+. The merge message is `Merge git://.../linux-2.6 into kernel/`.
+and make a merge commit, and register that resulting commit in
+the index file using `update-index --bind` instead of updating
+*any* branch head (remember, we do not use separate branches to
+keep track of subproject heads anymore).
^ permalink raw reply related
* Re: Notes on Subproject Support
From: Daniel Barkalow @ 2006-01-23 3:50 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Petr Baudis
In-Reply-To: <7v3bjfafql.fsf@assigned-by-dhcp.cox.net>
On Sun, 22 Jan 2006, Junio C Hamano wrote:
> Also note that in this round of proposal, there is no separate
> branches that keep track of heads of subprojects.
Interesting; I think it may become useful to allow for such heads, but we
can deal with that when it arises. (e.g., maybe you want to use topic
branches in the kernel development you do in the linux-2.6/ subdirectory
of your superproject working tree; so long as the core isn't using refs
for its own purposes, this is up to the user to keep straight and we can
help later when we have usage notes)
> ============
>
> Let's not forget to add the `Makefile`, and check the whole
> thing out from the index file.
> ------------
> $ git add Makefile
Maybe bind-projects should be "add-projects", to match "add", which has a
similar effect at the user level?
> $ git checkout-index -f -u -q -a
> ------------
>
> Now our directory should be identical with the `current`
> directory. After making sure of that, we should be able to
> commit the whole thing:
>
> ------------
> $ diff -x .git -r ../current ../combined
> $ git commit -m 'Initial toplevel project commit'
> ------------
>
> Which should create a new commit object that records what is in
> the index file as its tree, with `bind` lines to record which
> subproject commit objects are bound at what subdirectory, and
> updates the `$GIT_DIR/refs/heads/master`. Such a commit object
> might look like this:
> ------------
> tree 04803b09c300c8325258ccf2744115acc4c57067
Does this tree include trees for the bound projects?
> bind 5b2bcc7b2d546c636f79490655b3347acc91d17f linux-2.6/
> bind 0bdd79af62e8621359af08f0afca0ce977348ac7 appliance/
> author Junio C Hamano <junio@kernel.org> 1137965565 -0800
> committer Junio C Hamano <junio@kernel.org> 1137965565 -0800
>
> Initial toplevel project commit
> ------------
>
>
> Making further commits
> ----------------------
>
> The easiest case is when you updated the Makefile without
> changing anything in the subprojects. In such a case, we just
> need to create a new commmit object that records the new tree
> with the current `HEAD` as its parent, and with the same set of
> `bind` lines.
>
> When we have changes to the subproject part, we would make a
> separate commit to the subproject part and then record the whole
> thing by making a commit to the toplevel project. The user
> interaction might go this way:
> ------------
> $ git commit
> error: you have changes to the subproject bound at linux-2.6/.
> $ git commit --subproject linux-2.6/
> $ git commit
> ------------
I think "cd linux-2.6 && git commit" should work for the subproject, too,
but that can be a later enhancement.
> With the new `--subproject` option, the directory structure
> rooted at `linux-2.6/` part is written out as a tree, and a new
> commit object that records that tree object with the commit
> bound to that portion of the tree (`5b2bcc7b` in the above
> example) as its parent is created.
And the commit is written to the index, in the special slot for the
subproject, replacing its parent, I assume.
> Switching branches
> ------------------
>
> Along with the traditional two-way merge by `read-tree -m -u`,
> we would need to look at:
>
> . `bind` lines in the current `HEAD` commit.
>
> . `bind` lines in the commit we are switching to.
>
> . subproject binding information in the index file.
>
> to make sure we do sensible things.
This is one place I think storing the bindings in the commit is awkward.
read-tree deals in trees (hence the name), but will need information from
the commit.
I think it should be possible to hide the existance of subtrees in an
add-on to the struct tree API such that code that doesn't handle it
specifically doesn't see a difference, similarly to how the index file can
be handled. (parse_tree would fill out the structure as if the subproject
were a tree instead of a commit, assuming that the structure it's
pretending to be is the full tree, but there would be an additional
field for the commit if it's a subproject, until we've gone through
everything to make it work with subprojects).
I'm hoping to kill off the other tree object parser, which is only used by
ls-tree and diff-index at this point, but my workstation's home directory
hard drive seems to have gotten weirdly messed up at the hardware level
(and seems to have lost a lot of the contents of unused storage, or
something), so this may take a little while. At that point, whatever
special things we do in tree objects can be handled automatically with
changes only to a single location.
-Daniel
*This .sig left intentionally blank*
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox