Git development
 help / color / mirror / Atom feed
* Comments on "Understanding Version Control" by Eric S. Raymond
From: Jakub Narebski @ 2009-02-02 18:48 UTC (permalink / raw)
  To: git; +Cc: Eric S. Raymond

Some time ago I have found "Understanding Version-Control Systems"
(http://www.catb.org/esr/writings/version-control/version-control.html)
paper (in draft version) by Eric S. Raymond, via Joel Spolsky
recommendation in 'Podcast #37' entry in Stackoverflow Blog
(http://blog.stackoverflow.com/2009/01/podcast-37/).

This is a very nice _survey paper_, currently in development, so we
now have time for comments, corrections and suggestions.  It is
currently on hiatus (it seems that last changes are from the Jan 2008,
slightly more than a year ago), but it is not abandoned.  ESR plans to
get back to it after Wesnoth and gpsd do their upcoming releases.

In my opinion the most important issue is concentrating on "container
identity" instead of on the underlying issue of renames in version
control, which includes intelligent, rename-aware merge; talk about
issues and not about possible solution.  I will concentrate on this
issue for now, and leave for example issue of workflows, and of VCS
history for possible later posts (it is long enough as is).


Below you can find my comments; quoted fragments of "Understanding
Version Control' essay are prefixed with 'UVC> '.  'TODO' refers to
http://www.catb.org/esr/writings/version-control/TODO.html

Please do participate in this discussion, especially if you have
something to say with respect to rename detection versus rename
tracking issue.  Thanks in advance.

----------------------------------------------------------------------
http://www.catb.org/esr/writings/version-control/version-control.html

(Independent comment)

[...]
UVC> This leads to an awkward case called a cross-merge which tends to
UVC> confuse history-aware merging tools.

First, please remember that history-unaware merging tools, such as CVS
merge operation (unless you manually specified points in history) have
trouble with much simpler case of repeated merging into the same
branch.

Second, I think that currently DVCS can deal with criss-cross merges
correctly; for example git with its recursive merge strategy, or so
called MarkMerge from revctrl.org [TODO: probably needs survey how
other modern merging DVCS, such as Mercurial, Bazaar or Monotone, deal
with criss-cross merges].

......................................................................

UVC> == Container identity ==
UVC> 
UVC> In a VCS with container identity, files and directories have a
UVC> stable internal identity, initialized when the file is added to
UVC> the repository. This identity follows them through renames and
UVC> moves. This means that filenames and directories are versioned,
UVC> so that it is possible (for example) for the VCS to notice while
UVC> doing a merge between branches that two files with different
UVC> names are descendants of the same file and do a smarter merge of
UVC> their contents.

This whole subsection confuses in my opinion the goal (rename-aware
merge) with means (container identity).  It should concentrate on
intelligent merging after renames or moves, not on the possible ways
to do this.  It should list what is expected of modern VCS to do,
_then_ enumerate possible ways to do this.

But the most important thing is that "container identity" is a wrong
idea to have: wholesame rename (or copy) of a file is just special
case of more generic code movement and copying.

UVC> 
UVC> Container identity can be implemented by giving each file in the
UVC> repository a 'true name' analogous to a Unix inode,
UVC> Alternatively, it can be implemented implicitly by keeping all
UVC> records of renames in history and chasing through them each time
UVC> the VCS needs to check what file X was called in revision Y.

If I understand correctly 'file-ids' is the way original Arch (and its
descendant) did this, and how Bazaar deal with file and directory
renames, while 'tracking renames' is what Mercurial doea (a bit
incompletly).

But there is _another_ way to have rename-aware merge, and it is how
Git does it.  Git uses heuristic similarity based _rename detection_
to find how to do a merge in presence of renames.  This works quite
well _in practice_ (but it makes it harder to _test_ this solution).


Those three ways of dealing with renames have the following advantages
and disadvantages:

1. Need to use VCS to do file and directory renames and copies.

   Both 'file-ids' and 'rename tracking' have this limitation.

   This means that you cannot use your favorite filemanager, or IDE
   if it doesn't have support for your VCS, or patches to move and
   copy files.  This might be migitated a bit by doing rename
   detection at commit time (Mercurial has this ability wia
   hg-addremove, but it is not automatic), but that means freezing
   current algorithm results; with rename detection you can always
   take advantage of improved rename detection.

2. Ability to improve and correct errors in rename information.

   Both 'file-ids' and 'rename tracking' make it hard to correct
   errors in rename info, for example if you incorrectly marked file
   as copy or rename, or forgot to mark it as copy or rename.  If
   'file-ids' info is stored separately from history, it is less of an
   issue, but I don't see how you can correct errorneous rename
   information in 'rename tracking' solution short of rewriting
   history.

   In the case of 'rename detection' solution Git uses it is
   non-issue, as no rename information is stored.

3. Dealing with independently (in separate branches) added files.
   (this issue can be found in "Test suite" section of TODO).

   Most 'file-ids' solution have the problem if you add the same file
   independently on different branches (e.g. adding file via patch).
   First, one of histories must vanish; second, you have to repeat
   resolution if file-id conflict for every repeated merge.

   From TODO 'rename tracking' solution used in Mercurial doesn't have
   this problem, and of course Git's 'rename detection' doesn't have
   it either.

   Note that there are different versions of this issue: same name
   and same contents, different name same contents (rename without
   ancestor), and same name different contents.  The last case is of
   issue with automatic file-ids if they are dependent on file name;
   first case is of issue with automatic file-ids if they depend on
   branch/comitter/time, i.e. if file-ids differ on different
   branches.  Note: same contents here migh mean _almost_ the same
   contents.

4. Creating new files in renamed directories.
   
   Usually called support for directory renames; the problem is if one
   side (on one branch) renames some directory, and other side (other
   branch) creates new files in the old-name directory.

   For 'file-ids' solution this means that there have to be 'file-ids'
   ('inodes') also for directories; Bazaar has this feature.  For
   'rename tracking' solution this means that rename information for
   directories has to be stored somewhere; from what I understand
   Mercurial with its filename-hashed storage and per-file stored
   rename information doesn't have this feature.  For both 'file-id'
   and 'rename tracking' ('rename info') solutions this I think
   usually mean that you have to do directory renames using VCS
   tools.

   Whle on first glance it might seem that 'rename detection' solution
   (like the one used in Git) cannot deal with this problem it is not
   true.  VCS employing similarity based rename detection can detect
   wholesame directory rename based on pattern of file renames, and
   can put new file in new-name of directory.  Moreover it should be
   able to deal quite sanely with more complicated cases like
   splitting or merging of directories.  _However_ Git currently does
   not support this case (but see the note below), although there were
   some preliminary patches adding 'wholesame directory rename
   detection', so it is not purely theoretical.

   Note: usually you cannot simply move file to new directory without
   any other changes, so automatic creating new file (from the point
   of view of branch we merge into) in new-name directory instead of
   old-name directory might be not a good idea without stopping for
   manual merge conflict resolution, as it would result in semantic
   conflict.
   
5. Misdetection of file renames, and remembering manual corrections.
   (this problem concerns only 'rename detection' solution).

   Of course 'rename detection' algorithm is not perfect.  It can find
   rename when there isn't any if many files consist mainly from
   identical boilerplate (e.g. copyright, license, etc.).  It can fail
   to detect rename if files differ too much (usually then files
   cannot be merged automatically anyway then), or if files are too
   small (like usually in simple test cases used).  In the presence of
   multiple file renames and copies it can assign move or copy source
   to the wrong file.

   Closely related to this issue is a problem of remembering manual
   corrections to rename detection algorithm, and manual hints (like
   inn 'rename tracking' solution).  Both were discussed on git
   mailing list, the former under the name of having git-rerere2,
   remembering (and reusing) of recorded resolutions of tree-level
   conflicted merge, but there were (if I remember correctly) no
   conclusion and no patches.
   
6. Performance bottlenecks in managing renames during merge

   I don't know if it is a problem for 'file-id' solution (Bazaar), or
   for 'rename tracking' solution (Mercurial), but with 'rename
   detection' (Git) if there were a lot of reorganization of directory
   hierarchy between merge points then merge can take a lot of time.


UVC> 
UVC> Absence of container identity has the symptom that file
UVC> rename/move operations have to be modeled as a file add followed
UVC> by a delete, with the deleted file's history magically copied
UVC> during the add. 

I don't know if this paragraph was meant to be about another issue
with renames in VCS, namely dealing with renames and copise during
history browsing (which includes both log of changes, and tracking
line-wise file history aka. annotate/blame/praise).


1. Rename-aware changelog (commit logs).

1.1. Showing renames in "<scm> log", i.e. in whole project history.

     The only complication with rename detection here is choosing a
     level of rename and copy detection, as it might be CPU
     intensive.  In Git those are 'detect renames', 'detect copies as
     well as renames' and 'find copies harder'. That can, of course,
     be configured.

1.2. Following renames in "<scm> log <filename>", i.e. in single file
     history.

     Currently Git does not support it very well.  There is '--follow'
     option for git-log, but it is more of a hack to have something
     similar to what for example Subversion provides, than a full
     solution.  It works for simple histories, and might fail for more
     complicated ones; it is not however fundamental limitation.  On
     can always use "git log -- <old name> <new name>"...

     On the other hand single file history should be for developers
     second-class citizen in VCS supporting changesets; full history
     is _more_ than sum of single-file histories.


2. Rename-aware line-wise file history (which means annotating file
   with history of each line, something like "cvs annotate").

   Note that line-wise file history cannot deal with _deletions_;
   it is a known limitation of this tool.

2.1. Following wholesame file renames and copying.

     The issue is not stopping at file rename when tracking where
     given line came from... and of course representing this
     information in blame/annotate output.  git-blame supports this,
     and I think other VCS (Bazaar, Mercurial) also does.
     [TODO: check this]

2.2. Following code movement and copying.

     You can request that git-blame detect moving lines in the file
     (e.g. moving around code), and detect lines copied from other
     files (code moved across files).  I think it is (together with
     ability to ignore changes in whitespace in blame/annotate)
     currently (?) feature unique to Git; it also shows that idea of
     "container identities" is limited and narrow-minded, and that one
     should think of file renames as of special case of code
     movement. 
     [TODO: perhaps sreenshot of "git gui blame" or equivalent?]
     [TODO: some stats about file renames and other code movement]

UVC> 
UVC> Usually VCSes that lack container identity also create parent
UVC> directories on the fly whenever a file is added or checked out, and
UVC> you cannot actually have an empty directory in a repository.
 
In my opinion this is an fundamentally unrelated issue.  The question
if directories are created and deleted on demand is behavioral issue;
note that _not_ having directories deleted on the fly result in higher
probability of file/directory conflict.

Git is a bit peculiar in this case: empty directories _can_ be
represented in repository, currently cannot be represented in (flat)
staging area aka. index although it shouldn't be too hard to add, and
are removed on the fly which again shouldn't be too hard to change.

Note that you can track empty directories in Git (even if VCS adds
them and removes them on the fly) by trick of having for example empty
'.gitignore' file in it.


UVC> == Snapshots vs. changesets ==
UVC> 
UVC> There are two ways to model the history of a line of
UVC> development. One is as a series of snapshots of an evolving tree
UVC> of files. The other is as a series of changesets transforming
UVC> that tree from a root state (usually empty) to a tip state.

Git is snapshot-based (although packed format uses [binary] delta
compression).  Mercurial is changeset-based.  Bazaar uses different
representation altogether, I think[1] (used to use weave); wiki
says that it is snapshot-oriented, but it has file-ids.

[1] http://bazaar-vcs.org/BazaarFormats is a bit lacking in details

[...]
UVC> Changeset-based systems have some further distinctions based on
UVC> what kinds of data a changeset carries. At minimum, a changeset
UVC> is a group of deltas to individual files, but there are
UVC> variations in what kind of file-tree operations are represented
UVC> in changesets.  

I think most famous _example_ here is Darcs, with _idea_ of other
operations than simple text delta, like "rename this variable to that
name" example in documentation, but I don't know if it was actually
went beyound hand-waving.

Note that patch commutation algebra of Darcs (most pure-changeset VCS
there, in my opinion) might look like a neat idea, but please remember
than Darcs had (and perhaps has still) exponential bad performance of
merging in some cases.

UVC> 
UVC> Changesets which include an explicit representation of
UVC> file/directory moves and renames make it easy to implement
UVC> container identity. (Container identity could also be implemented
UVC> as a separate sequence of transaction records running parallel to
UVC> a snapshot-sequence representation, but I know of no VCS that
UVC> actually does this.) 

[TODO: check how Bazaar actually does this, as it is example of VCS
with 'file-ids', also for directories; check where Mercurial stores
'rename tracking' information].

[...]
UVC> Snapshot and changesets are not perfectly dual
UVC> representations. It took a long time for VCS designers to notice
UVC> this; the broken symmetry was at the core of a well-known
UVC> argument between the designers of Arch and Subversion in 2003,
UVC> and did not begin to become widely understood until after Martin
UVC> Pool's 2004 essay "Integrals and Derivatives"[2]. Pool, a
UVC> co-author of bzr, correctly noted that attempts to stick with the
UVC> more intuitive sequence-of-snapshots representation have several
UVC> troubling consequences, including making container identity and
UVC> past merges between branches more difficult to track. 
UVC> 
UVC> [2] http://sourcefrog.net/weblog/software/vc/derivatives.html

Below there is excerpt from OLD "Integrals and Derivatives" essay
(I hope I have choosen relevant part of this essay).

ID> Working in terms of changesets, or at least having the option to
ID> do so allows more powerful operation. 
ID>
ID> For example, consider repeated merges among a related set of
ID> trees. Arch and Darcs [which work primarily in the changeset
ID> domain] handle this well, because they can easily remember which
ID> changesets have already come across. Subversion and CVS tend to
ID> handle it poorly, because merely tracking which version from the
ID> other tree has merged doesn't really capture the right
ID> information.

This is, as we now know, _wrong_.  Subversion and CVS handle this
poorly not because they are snapshot based (CVS most certainly is not:
it is file-level delta based), but because they do not store merge
information (which revisions were merged to form a merge commit,
i.e. all parents of a merge commit): they lack merge tracking.
Therefore it is not possible to find common ancestors (merge bases),
because there is no enough information.  _Not_ because being
snapshot-based.

Additionally it is now I think universally acknowledged that three-way
merge (with a bit extra to deal with possibly multiple merge-bases,
e.g. in presence of criss-cross merge) is superior merge algorithm; it
is certainly superior to "reapply patches skipping already present"
algorithm that quoted example seems to imply that Arch and Darcs use
(which is available in modern DVCS as well, either as some patch
management / patch queue extensions, or as git-rebase, Mercurial's
transplant extension, or Bazaar graft extension).


[...]

UVC> = What, if anything, have we learned from history? =
UVC> 
UVC> There's a folk saying that "It's not what you don't know that
UVC> hurts you, it's what you think you know that ain't so." In
UVC> examining the pattern of development of VCSes, it seems to me
UVC> that the this sub-field of computer science has been less
UVC> hampered than most by difficulties in finding appropriate
UVC> techniques, but more hampered than most by wrong assumptions that
UVC> hung on far longer than they should have. 
UVC> 
UVC> First wrong assumption: Conflict resolution by merging is
UVC> intractably difficult, so we'll have to settle for locking. It
UVC> took at least fifteen and arguably twenty years for VCS designers
UVC> to get shut of that one. But it's historical now. 

I'm not sure if it has place here, i.e. if it was wrong assumption or
just lack of thought, but I would emphasize that commit-before-merge 
is much, much better than merge-before-commit (or update-before-commit, 
as it is _implicit_ merge that might need to be performed) workflow.

One of more important tasks of VCS is to not lose your changes; the
merge-before-commit does not fill this tack completely, in my opinion,
especially nowadays with networked large-group collarboration.

?merge strategies

UVC> 
UVC> Second wrong assumption: Change history representation as a
UVC> snapshot sequence is perfectly dual to the representation as
UVC> change/add/delete/rename sequences.. This folk theorem is well
UVC> expressed in the 2004 essay "On Arch and Subversion"[3]. It is
UVC> appealing, widely held, and dead wrong.
UVC> 
UVC> File renames break the apparent symmetry. The failure of
UVC> snapshot-based models to correctly address this has caused
UVC> endless design failures, subtle bugs, and user misery. 

It is not true.  Example of snapshot-based Git, which with its rename
detection deals very well in practice with file renames contradict
this theory.  Bazaar which is supposedly snapshot-based, yet support
"container identities" ('file-ids') contradict this further.

The symmetry might be broken _only_ if there are other operators in
changesets than simple delta.  But one has to remember that if
representation is not equivalent to snapshot-based in the sense that
you can do a (3-way) merge based on endpoints (branches to be merged
and common ancestors aka. merge bases) _only_, and one has to take
_whole_ history into account when merging, then there is high
probability of cases where merge performance suffers badly.  Please do
not say that performance doesn't matter, because it does; if operation
takes minutes (or more) rather than seconds, then this would affect
workflow used, as people try to avoid unpleasant operations...

UVC> 
UVC> Practically speaking, failure to address this broken symmetry
UVC> goes a long way towards explaining why CVS became such a
UVC> disaster. But the damage didn't end there, which is why I'm
UVC> courting controversy by pointing out that it underlies a debate
UVC> about third-generation designs that is still live today. Should
UVC> VCSes be purely content-addressable filesystems (the Mercurial
UVC> and git approach) or should they have container identity (as in
UVC> Arch, monotone, and bzr)? 
UVC> 
UVC> That debate is not over, but at least VCS designers are grappling
UVC> with it now. 

CVS was disaster no because of (supposedly) broken symmetry between
snapshots (snapshot-based operations) and changesets; it was disaster
because its per-file versioning roots were very visible in: non-atomic
commits, no support for changesets, heavyweight (bad performance)
branching and tagging, complete lack of support for renaming files.

I agree that the issue of content-addressed filesystem versus
container identities (file-ids) is importnat one, but I would say that
neither one won over the other...

UVC> 
UVC> I have a guess about the third wrong assumption. I think it goes
UVC> something like this: The correct choice of abstractions,
UVC> operations, and containers in a VCS is the one that makes the
UVC> cleverest sorts of data-shuffling possible. I suspect that, as
UVC> our algorithms get better, we're going to find that the best
UVC> choices are not the most theoretically clever ones but rather the
UVC> ones that are easiest for human beings to intuitively model.

Here I think Git model wins hands down: DAG (Direct Acyclic Graph) of
commits (Monotone had it first), clear relation between objects:
commit object contain metadata (like authorship and commit message),
link to zero (root / initial commit), one (usual case), or two or more
(merges) parent commits, and link to snapshot of a state of project at
given version...  Having branches and tags (and remote-tracking
branches) to exist outside DAG of commits, in a separate namespaces;
not that disaster of conventions for imitation of branches and tags in
Subversion.  Having current branch to be just pointer to one of
branches.

Having delta compression "under the hood" in the packfiles allow to
combine efficiency of deltas for repository size and network transfer,
while retaining very clear model of snapshot-based VCS.

UVC> 
UVC> This is why, even though I find constructions like today's
UVC> elaborate merge theory[4] fascinating to think about. I'm not
UVC> sure it is actually going anywhere useful. Naive merge algorithms
UVC> with poor behavior at edge cases may actually be preferable to
UVC> more sophisticated ones that handle edge cases well but that
UVC> humans have trouble anticipating effectively.

Nowadays even Bram Cohen of Codeville merge agrees[citation needed]
that 3-way merge, which works in most cases but if fails is easy to
understood, is better than advanced merge strategies which can merge
automatically a few more cases, but if fail it is difficult to resolve
conflicts; note that fail include wrongly resolved merge (merge
strategy gives no conflicts, but merge resolution is wrong).

UVC> 
UVC> An even more interesting question is this: what are the fourth,
UVC> and nth wrong assumptions --- the ones we haven't noticed we're
UVC> making yet? 
UVC> 
UVC> [3] http://www.reverberate.org/computers/ArchAndSVN.html
UVC> [4] http://revctrl.org/CategoryMergeAlgorithm

Well, Linus probably would say that concentrating on special case of
file and directory renames instead of more generic code movement is a
wrong idea.

But what do YOU think?

-- 
Jakub Narebski
Poland

^ permalink raw reply

* [PATCH] builtin-remote: make rm operation safer in mirrored repository
From: Jay Soffian @ 2009-02-02 18:40 UTC (permalink / raw)
  To: git; +Cc: Jay Soffian, Jeff King, Junio C Hamano
In-Reply-To: <76718490902020536g6f4bcee2i76ee046a8dc7d46@mail.gmail.com>

"git remote rm <repo>" is happy to remove non-remote branches (and their
reflogs). This may be okay if the repository truely is a mirror, but if the
user had done "git remote add --mirror <repo>" by accident and was just
undoing their mistake, then they are left in a situation that is difficult to
recover from.

After this commit, "git remote rm" skips over non-remote branches and instead
advises the user on how to remove such branches using "git branch -d", which
itself has nice safety checks wrt to branch removal lacking from "git remote
rm".

Signed-off-by: Jay Soffian <jaysoffian@gmail.com>
---
This version adds a test case. I also noticed that the check I'd added to
add_branch_for_removal() was generating spurious warnings because I'd added it
in the wrong place; this version moves the check below the
remote_find_tracking() checks.

 builtin-remote.c  |    7 +++++++
 t/t5505-remote.sh |    9 +++++++++
 2 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/builtin-remote.c b/builtin-remote.c
index abc8dd8..571caff 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -323,6 +323,13 @@ static int add_branch_for_removal(const char *refname,
 			return 0;
 	}
 
+	/* don't delete non-remote branches */
+	if (prefixcmp(refname, "refs/remotes")) {
+		warning("not removing non-remote branch; use git branch -d %s to remove",
+			abbrev_branch(refname));
+		return 0;
+	}
+
 	/* make sure that symrefs are deleted */
 	if (flags & REF_ISSYMREF)
 		return unlink(git_path("%s", refname));
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index 1f59960..80d40ea 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -107,6 +107,15 @@ test_expect_success 'remove remote' '
 )
 '
 
+test_expect_success 'remove remote protects non-remote branches' '
+(
+	cd test &&
+	git config --add remote.oops.fetch "+refs/*:refs/*" &&
+	git remote rm oops 2>stderr &&
+	grep "not removing non-remote branch.* master " stderr
+)
+'
+
 cat > test/expect << EOF
 * remote origin
   URL: $(pwd)/one
-- 
1.6.1.2.308.gd57ba

^ permalink raw reply related

* [PATCH v2 1/2] Introduce config variable "diff.primer"
From: Keith Cascio @ 2009-02-02 18:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <1233598855-1088-1-git-send-email-keith@cs.ucla.edu>

Introduce config variable "diff.primer".
Improve porcelain diff's accommodation of user preference by allowing
some settings to (a) persist over all invocations and (b) stay consistent
over multiple tools (e.g. command-line and gui).  The approach taken here
is good because it delivers the consistency a user expects without breaking
any plumbing.  It works by allowing the user, via git-config, to specify
arbitrary options to pass to porcelain diff on every invocation, including
internal invocations from other programs, e.g. git-gui.  Introduce diff
command-line options --primer and --no-primer.  Affect only porcelain diff:
we suppress primer options for plumbing diff-{files,index,tree},
format-patch, and all other commands unless explicitly requested using
--primer (opt-in).  Teach gitk to use --primer, but protect it from
inapplicable options like --color.

Signed-off-by: Keith Cascio <keith@cs.ucla.edu>
---
 Documentation/config.txt       |   14 +++++++
 Documentation/diff-options.txt |   10 +++++
 builtin-diff.c                 |    2 +
 diff.c                         |   77 +++++++++++++++++++++++++++++++++++-----
 diff.h                         |   14 ++++++--
 gitk-git/gitk                  |   16 ++++----
 6 files changed, 113 insertions(+), 20 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index e2b8775..bd85c4a 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -601,6 +601,20 @@ diff.autorefreshindex::
 	affects only 'git-diff' Porcelain, and not lower level
 	'diff' commands, such as 'git-diff-files'.
 
+diff.primer::
+	Whitespace-separated list of options to pass to 'git-diff'
+	on every invocation, including internal invocations from
+	linkgit:git-gui[1] and linkgit:gitk[1],
+	e.g. `"--patience --color --ignore-space-at-eol --exit-code"`.
+	See linkgit:git-diff[1]. You can suppress these at run time with
+	option `--no-primer`.  Supports a subset of
+	'git-diff'\'s many options, at least:
+	`-b --binary --color --color-words --cumulative --dirstat-by-file
+--exit-code --ext-diff --find-copies-harder --follow --full-index
+--ignore-all-space --ignore-space-at-eol --ignore-space-change
+--ignore-submodules --no-color --no-ext-diff --no-textconv --patience -q
+--quiet -R -r --relative -t --text --textconv -w`
+
 diff.external::
 	If this config variable is set, diff generation is not
 	performed using the internal diff machinery, but using the
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 813a7b1..f422055 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -254,5 +254,15 @@ override configuration settings.
 --no-prefix::
 	Do not show any source or destination prefix.
 
+--no-primer::
+	Ignore default options specified in '.git/config', i.e.
+	those that were set using a command like
+	`git config diff.primer "--patience --color --ignore-space-at-eol --exit-code"`
+
+--primer::
+	Opt-in for default options specified in '.git/config'.  This option is
+	most often used with the three plumbing commands diff-{files,index,tree}.
+	These commands normally suppress default options.
+
 For more detailed explanation on these common options, see also
 linkgit:gitdiffcore[7].
diff --git a/builtin-diff.c b/builtin-diff.c
index d75d69b..b3c3e87 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -284,6 +284,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 
 	init_revisions(&rev, prefix);
 
+	DIFF_OPT_SET(&rev.diffopt, PRIMER);
+
 	/* If this is a no-index diff, just run it and exit there. */
 	diff_no_index(&rev, argc, argv, nongit, prefix);
 
diff --git a/diff.c b/diff.c
index a5a540f..32455c3 100644
--- a/diff.c
+++ b/diff.c
@@ -26,6 +26,8 @@ static int diff_suppress_blank_empty;
 int diff_use_color_default = -1;
 static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
+static const char *diff_primer;
+static struct diff_options *primer;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 
@@ -106,6 +108,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
 		diff_rename_limit_default = git_config_int(var, value);
 		return 0;
 	}
+	if (!strcmp(var, "diff.primer"))
+		return git_config_string(&diff_primer, var, value);
 
 	switch (userdiff_config(var, value)) {
 		case 0: break;
@@ -2316,6 +2320,45 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 	builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
 }
 
+static const char blank[] = " \t\r\n";
+
+void parse_diff_primer(struct diff_options *options)
+{
+	char *str1, *token, *saveptr;
+	int len;
+
+	if ((! diff_primer) || ((len = (strlen(diff_primer)+1)) < 3))
+		return;
+
+	token = str1 = strncpy((char*) malloc(len), diff_primer, len);
+	if ((saveptr = strpbrk(token += strspn(token, blank), blank)))
+		*(saveptr++) = '\0';
+	while (token) {
+		if (*token == '-')
+			diff_opt_parse(options, (const char **) &token, -1);
+		if ((token = saveptr))
+			if ((saveptr = strpbrk(token += strspn(token, blank), blank)))
+				*(saveptr++) = '\0';
+	}
+
+	free( str1 );
+}
+
+struct diff_options* flatten_diff_options(struct diff_options *master, struct diff_options *slave)
+{
+	unsigned x0 = master->flags, x1 = master->mask, x2 = slave->flags, x3 = slave->mask;
+	long w = master->xdl_opts, x = master->xdl_mask, y = slave->xdl_opts, z = slave->xdl_mask;
+
+	//minimized by Quine-McCluskey
+	master->flags = (~x1&x2&x3)|(x0&~x3)|(x0&x1);
+	master->mask = x1|x3;
+
+	master->xdl_opts = (~x&y&z)|(w&~z)|(w&x);
+	master->xdl_mask = x|z;
+
+	return master;
+}
+
 void diff_setup(struct diff_options *options)
 {
 	memset(options, 0, sizeof(*options));
@@ -2326,14 +2369,15 @@ void diff_setup(struct diff_options *options)
 	options->break_opt = -1;
 	options->rename_limit = -1;
 	options->dirstat_percent = 3;
-	DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
+	if (DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE))
+		DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
 	options->context = 3;
 
 	options->change = diff_change;
 	options->add_remove = diff_addremove;
 	if (diff_use_color_default > 0)
 		DIFF_OPT_SET(options, COLOR_DIFF);
-	else
+	else if (DIFF_OPT_TST(options, COLOR_DIFF))
 		DIFF_OPT_CLR(options, COLOR_DIFF);
 	options->detect_rename = diff_detect_rename_default;
 
@@ -2423,6 +2467,14 @@ int diff_setup_done(struct diff_options *options)
 		DIFF_OPT_SET(options, EXIT_WITH_STATUS);
 	}
 
+	if (DIFF_OPT_TST(options, PRIMER)) {
+		if (! primer) {
+			diff_setup(primer = (struct diff_options *) malloc(sizeof(struct diff_options)));
+			parse_diff_primer(primer);
+		}
+		flatten_diff_options(options, primer);
+	}
+
 	return 0;
 }
 
@@ -2570,13 +2622,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 
 	/* xdiff options */
 	else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
-		options->xdl_opts |= XDF_IGNORE_WHITESPACE;
+		DIFF_XDL_SET(options, IGNORE_WHITESPACE);
 	else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
-		options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+		DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
 	else if (!strcmp(arg, "--ignore-space-at-eol"))
-		options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+		DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
 	else if (!strcmp(arg, "--patience"))
-		options->xdl_opts |= XDF_PATIENCE_DIFF;
+		DIFF_XDL_SET(options, PATIENCE_DIFF);
 
 	/* flags options */
 	else if (!strcmp(arg, "--binary")) {
@@ -2597,10 +2649,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 		DIFF_OPT_SET(options, COLOR_DIFF);
 	else if (!strcmp(arg, "--no-color"))
 		DIFF_OPT_CLR(options, COLOR_DIFF);
-	else if (!strcmp(arg, "--color-words"))
-		options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+	else if (!strcmp(arg, "--color-words")) {
+		DIFF_OPT_SET(options, COLOR_DIFF);
+		DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+	}
 	else if (!prefixcmp(arg, "--color-words=")) {
-		options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+		DIFF_OPT_SET(options, COLOR_DIFF);
+		DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
 		options->word_regex = arg + 14;
 	}
 	else if (!strcmp(arg, "--exit-code"))
@@ -2617,6 +2672,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 		DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
 	else if (!strcmp(arg, "--ignore-submodules"))
 		DIFF_OPT_SET(options, IGNORE_SUBMODULES);
+	else if (!strcmp(arg, "--primer"))
+		DIFF_OPT_SET(options, PRIMER);
+	else if (!strcmp(arg, "--no-primer"))
+		DIFF_OPT_CLR(options, PRIMER);
 
 	/* misc options */
 	else if (!strcmp(arg, "-z"))
diff --git a/diff.h b/diff.h
index 23cd90c..7f11b12 100644
--- a/diff.h
+++ b/diff.h
@@ -66,9 +66,15 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_DIRSTAT_CUMULATIVE  (1 << 19)
 #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
 #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
-#define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
-#define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
-#define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag)
+#define DIFF_OPT_PRIMER              (1 << 22)
+#define DIFF_OPT_TST(opts, flag)    ((opts)->flags &   DIFF_OPT_##flag)
+#define DIFF_OPT_SET(opts, flag)    ((opts)->flags |=  DIFF_OPT_##flag), ((opts)->mask |= DIFF_OPT_##flag)
+#define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag), ((opts)->mask |= DIFF_OPT_##flag)
+#define DIFF_OPT_DRT(opts, flag)    ((opts)->mask  &   DIFF_OPT_##flag)
+#define DIFF_XDL_TST(opts, flag)    ((opts)->xdl_opts &   XDF_##flag)
+#define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |=  XDF_##flag), ((opts)->xdl_mask |= XDF_##flag)
+#define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag), ((opts)->xdl_mask |= XDF_##flag)
+#define DIFF_XDL_DRT(opts, flag)    ((opts)->xdl_mask &   XDF_##flag)
 
 struct diff_options {
 	const char *filter;
@@ -77,6 +83,7 @@ struct diff_options {
 	const char *single_follow;
 	const char *a_prefix, *b_prefix;
 	unsigned flags;
+	unsigned mask;
 	int context;
 	int interhunkcontext;
 	int break_opt;
@@ -95,6 +102,7 @@ struct diff_options {
 	int prefix_length;
 	const char *stat_sep;
 	long xdl_opts;
+	long xdl_mask;
 
 	int stat_width;
 	int stat_name_width;
diff --git a/gitk-git/gitk b/gitk-git/gitk
index dc2a439..b67bbaa 100644
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -4259,7 +4259,7 @@ proc do_file_hl {serial} {
 	# must be "containing:", i.e. we're searching commit info
 	return
     }
-    set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
+    set cmd [concat | git diff-tree --primer --no-color -r -s --stdin $gdtargs]
     set filehighlight [open $cmd r+]
     fconfigure $filehighlight -blocking 0
     filerun $filehighlight readfhighlight
@@ -4753,7 +4753,7 @@ proc dodiffindex {} {
 
     if {!$showlocalchanges || !$isworktree} return
     incr lserial
-    set cmd "|git diff-index --cached HEAD"
+    set cmd "|git diff-index --primer --no-color --cached HEAD"
     if {$vfilelimit($curview) ne {}} {
 	set cmd [concat $cmd -- $vfilelimit($curview)]
     }
@@ -4782,7 +4782,7 @@ proc readdiffindex {fd serial inst} {
     }
 
     # now see if there are any local changes not checked in to the index
-    set cmd "|git diff-files"
+    set cmd "|git diff-files --primer --no-color"
     if {$vfilelimit($curview) ne {}} {
 	set cmd [concat $cmd -- $vfilelimit($curview)]
     }
@@ -7068,7 +7068,7 @@ proc diffcmd {ids flags} {
     if {$i >= 0} {
 	if {[llength $ids] > 1 && $j < 0} {
 	    # comparing working directory with some specific revision
-	    set cmd [concat | git diff-index $flags]
+	    set cmd [concat | git diff-index --primer --no-color $flags]
 	    if {$i == 0} {
 		lappend cmd -R [lindex $ids 1]
 	    } else {
@@ -7076,13 +7076,13 @@ proc diffcmd {ids flags} {
 	    }
 	} else {
 	    # comparing working directory with index
-	    set cmd [concat | git diff-files $flags]
+	    set cmd [concat | git diff-files --primer --no-color $flags]
 	    if {$j == 1} {
 		lappend cmd -R
 	    }
 	}
     } elseif {$j >= 0} {
-	set cmd [concat | git diff-index --cached $flags]
+	set cmd [concat | git diff-index --primer --no-color --cached $flags]
 	if {[llength $ids] > 1} {
 	    # comparing index with specific revision
 	    if {$i == 0} {
@@ -7095,7 +7095,7 @@ proc diffcmd {ids flags} {
 	    lappend cmd HEAD
 	}
     } else {
-	set cmd [concat | git diff-tree -r $flags $ids]
+	set cmd [concat | git diff-tree --primer --no-color -r $flags $ids]
     }
     return $cmd
 }
@@ -10657,7 +10657,7 @@ if {[catch {package require Tk 8.4} err]} {
 }
 
 # defaults...
-set wrcomcmd "git diff-tree --stdin -p --pretty"
+set wrcomcmd "git diff-tree --primer --no-color --stdin -p --pretty"
 
 set gitencoding {}
 catch {
-- 
1.6.1

^ permalink raw reply related

* Re: git grep -I bug
From: Jeremy O'Brien @ 2009-02-02 18:26 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vwsc8hgh4.fsf@gitster.siamese.dyndns.org>

[-- Attachment #1: Type: text/plain, Size: 601 bytes --]

On Mon, Feb 02, 2009 at 09:54:31AM -0800, Junio C Hamano wrote:
> Jeremy O'Brien <obrien654j@gmail.com> writes:
> 
> > I am running git version 1.6.1.2.309.g2ea3.
> >
> > When I use
> >
> > git grep -I "string_to_match"
> >
> > to ignore binary files in my grep, binary files are returned anyway.
> 
> One sanity check.  What does 'git grep --cached -I "string_to_match"' do
> in that case?
> 

It works as expected. It is interesting that while my Linux install was
affected by this bug, my Mac OS X install did not seem to be affected by
it, while running the same version of git.

[-- Attachment #2: Type: application/pgp-signature, Size: 194 bytes --]

^ permalink raw reply

* Re: Newbie question regarding 3way merge order.
From: Raimund Berger @ 2009-02-02 18:15 UTC (permalink / raw)
  To: git
In-Reply-To: <49871ADA.4080905@viscovery.net>

Johannes Sixt <j.sixt@viscovery.net> writes:

> Please don't set Mail-Followup-To: here, and keep the Cc: list.
>
> Raimund Berger schrieb:
>> do the following conditions hold
>> 
>> (i)  A+B == B+A for all commits A,B
>> (ii) (A+B)+C == A+(B+C) for all A,B,C
>> 
>> where "+" designates the standard git 3way merge?
>
> I don't think that (ii) does holds in general.
>
> [ In the examples consider each letter/symbol on a line by itself; this
> saves vertical space. ]
>
> Start with this (the merge base):
>
> 	f(a)
>
> and there are three topic branches growing from here:
> A makes this (rename f->g):
>
> 	g(a)
>
> B makes this (add another f):
>
> 	f(a)f(b)
>
> C makes this (renames a->c):
>
> 	f(c)
>
> Then A+B is
>
> 	g(a)f(b)
>
> A+C is
>
> 	g(c)
>
> B+C is
>
> 	f(c)f(b)
>
> (A+B)+C is
>
> 	g(c)f(b)
>
> but A+(B+C) is ambiguous:
>
> 	g(c)f(b)
> or
> 	f(c)g(b)
>
> -- Hannes


Are you sure you're not making assumptions about "obvious" manual
resolutions? E.g. I can't quite see how A+B, which is

      g(a)----
     /        \
 f(a)          g(a)f(b) or f(a)f(b) ???
     \        /
      f(a)f(b)

would not be flagged as a conflict regarding f(a) vs. g(a).

Now, you may assume that because B leaves f(a) as it is while A changes
f(a) into g(a) that both, that change and the addition of f(b) in B,
should survive the merge. But the actual algorithm couldn't possibly
know and decide that. Same goes for other merges you do there. In fact,
in strict terms of how I defined equality, you didn't give a counter
example because neither (A+B)+C or A+(B+C) automatically resolve. It
would have been if one of them did.

And that's why I specifically "limited" my equality relation to
automatic resolutions, to simplify the discussion and deal with kind of
minimum requirements first. I didn't even mention that originally
because I felt it was so obvious.

So only the next step would be looking at how manual resolutions play
into this, and while that might be fairly intuitive in the A+B == B+A
commutativity case, associativity of course is kind of a different
ballpark. Although I'd expect somebody intimately acquainted with
merging techniques capable of maybe giving a hint or two even in that
case.

^ permalink raw reply

* Re: git grep -I bug
From: Junio C Hamano @ 2009-02-02 17:54 UTC (permalink / raw)
  To: Jeremy O'Brien; +Cc: git
In-Reply-To: <20090202174257.GA8259@Ambelina.erc-wireless.uc.edu>

Jeremy O'Brien <obrien654j@gmail.com> writes:

> I am running git version 1.6.1.2.309.g2ea3.
>
> When I use
>
> git grep -I "string_to_match"
>
> to ignore binary files in my grep, binary files are returned anyway.

One sanity check.  What does 'git grep --cached -I "string_to_match"' do
in that case?

If it works as expected but without --cached it doesn't, then I think the
following patch will fix it.

-- >8 --
Subject: grep: pass -I (ignore binary) down to external grep

The external-grep codepath forgets to pass this option.  Fix it.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-grep.c |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

diff --git c/builtin-grep.c w/builtin-grep.c
index bebf15c..c799fdd 100644
--- c/builtin-grep.c
+++ w/builtin-grep.c
@@ -297,6 +297,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
 		push_arg("-l");
 	if (opt->unmatch_name_only)
 		push_arg("-L");
+	if (opt->binary == GREP_BINARY_NOMATCH)
+		push_arg("-I");
 	if (opt->null_following_name)
 		/* in GNU grep git's "-z" translates to "-Z" */
 		push_arg("-Z");

^ permalink raw reply related

* duplicate sign-off-by error
From: Sharib Khan @ 2009-02-02 17:22 UTC (permalink / raw)
  To: git

I m getting a Duplicate Sign-off by error when trying to commit to the  
repository.
I am using git 1.5.6 on solaris

what is this error related to and how can it be resolved.

^ permalink raw reply

* git grep -I bug
From: Jeremy O'Brien @ 2009-02-02 17:42 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 291 bytes --]

I am running git version 1.6.1.2.309.g2ea3.

When I use

git grep -I "string_to_match"

to ignore binary files in my grep, binary files are returned anyway.

When I do a regular

grep "string_to_match" *

grep does not show the binary files. I believe this is a bug.

Thanks,
Jeremy O'Brien

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply

* Re: Newbie question regarding 3way merge order.
From: Johannes Sixt @ 2009-02-02 16:10 UTC (permalink / raw)
  To: Raimund Berger; +Cc: git, Junio C Hamano
In-Reply-To: <871vugc2c8.fsf@gigli.quasi.internal>

Please don't set Mail-Followup-To: here, and keep the Cc: list.

Raimund Berger schrieb:
> do the following conditions hold
> 
> (i)  A+B == B+A for all commits A,B
> (ii) (A+B)+C == A+(B+C) for all A,B,C
> 
> where "+" designates the standard git 3way merge?

I don't think that (ii) does holds in general.

[ In the examples consider each letter/symbol on a line by itself; this
saves vertical space. ]

Start with this (the merge base):

	f(a)

and there are three topic branches growing from here:
A makes this (rename f->g):

	g(a)

B makes this (add another f):

	f(a)f(b)

C makes this (renames a->c):

	f(c)

Then A+B is

	g(a)f(b)

A+C is

	g(c)

B+C is

	f(c)f(b)

(A+B)+C is

	g(c)f(b)

but A+(B+C) is ambiguous:

	g(c)f(b)
or
	f(c)g(b)

-- Hannes

^ permalink raw reply

* Re: cvs2git migration - cloning CVS repository
From: Rostislav Svoboda @ 2009-02-02 16:05 UTC (permalink / raw)
  To: git; +Cc: David Syzdek, smurf
In-Reply-To: <9a0027270902020707p4305d92fl1388bbfeb8a0864b@mail.gmail.com>

2009/2/2 David Syzdek <syzdek@gmail.com>:
> Rostislav,
> I ran into this issue when I  tried to import my CVS repository into Git.
>  Be sure to issue a CVS login before attempting the import:
>
> cvs -d :pserver:mylogin@cvs-server:/cvs/cvsrepository login

Yea! that's it! THX!
IMHO it won't be bad to have it somewhere in the git-cvsimport and/or
gitcvs-migration
documentation (short notice would be enough) even if the 'AuthReply: I
HATE YOU'
comes out of git. It would save me more than a couple of hours if it
were there...

Bost

^ permalink raw reply

* Autoscout24 Auto Offer
From: Uwe Rise @ 2009-02-02 15:42 UTC (permalink / raw)
  To: git


Hola, 

Estoy muy interesado en sus coches pero soy confundo, porque veo los mismos coches con las mismas fotos encendido www.autoscout24.de, de otro vendedor (Auto-Distribuidor autorizado). ¿Es usted vendedor falso (tratante)? 
Éste es el indentification de los coches falsos:
http://85.120.99.96/autoscout24.de/master_login/CustomerLogin.html

http://85.120.99.96/autoscout24.de/master_login/CustomerLogin.html

^ permalink raw reply

* Re: Newbie question regarding 3way merge order.
From: Raimund Berger @ 2009-02-02 14:58 UTC (permalink / raw)
  To: git
In-Reply-To: <7vskmyt127.fsf@gitster.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> writes:

[snip]

> This is where you need to use the tool right to get the most out of it.
>
> You could do this in addition to (2).
>
>  (3) Because B is an independent bug, you can have its own topic to fix it
>      and merge it to the test integration, planning to merge it later to
>      mainline independently from feature A topic.  But you already know
>      feature A depends on bugfix B to work correctly, so you merge the fix
>      to the feature as well in advance.
>
>       ---o-------*---*   test integration branch
>         /       /   / 
>        /       /   o     bugfix B
>       /       /   / \
>      /       o-------*   feature A
>     /       /   /
>    o---o---o---o---o     mainline

You seem to suggest that juggling merges is possible in the exact way I
was inquiring about. So let me ask again:

if M1 and M2 are merges and I define equality of merges (M1==M2) to be

- M1 resolves automatically (on the textual level) iff (if and only if)
  M2 does
- if either resolves automatically, the tree produced by M1 is the same
  as that of M2 (the tree SHAs are the same)

do the following conditions hold

(i)  A+B == B+A for all commits A,B
(ii) (A+B)+C == A+(B+C) for all A,B,C

where "+" designates the standard git 3way merge?


[snip]

> If you end up merging A first and then want to merge B later (or the other
> way around, merge B and then A), and if the second merge to the mainline
> causes huge textual conflicts, you can instead merge the conslidated topic
> A+B to the mainline.

One could imagine (corporate) policies though which might try to map
organizational "entities" (tasks, teams, according responsibilities) to
branches. I.e. contexts where conflict resolution through merging might
not always be desired and they'd rather be sought on a branch, i.e. by
producing a commit on either branch which resolves a possible
conflict. That's why I asked how rebase compares to merge and whether
the merge machinery underlying the former is exactly the same. I seem to
understand by now though that it is not.

^ permalink raw reply

* [wishlist] git-archive -L
From: Pierre Habouzit @ 2009-02-02 14:34 UTC (permalink / raw)
  To: rene.scharfe; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1596 bytes --]

Hi Rene,

I wanted to do that myself, but I sadly miss the time right now, so I
wonder if you'd know how to do the following.

We have in our repository a kind of modular system (for a family of web
sites) where each web-site uses a (versionned) symlink farm. IOW it
works basically that way:

    www/module1
    www/module2
    product_A/www/module1 -> ../../www/module1
    product_A/www/module_A
    product_B/www/module1 -> ../../www/module1
    product_B/www/module2 -> ../../www/module2
    product_B/www/module_B

Though product_A and _B even if they share a fair amount of code, are
separate products and when we release, we'd like to be able to perform
from inside:

    git archive --format=tar -L product_$A

where -L basically does what it does in cp: dereference symlinks.  To
make the thing hairier, we also have symlinks _inside_ www/ (pointing
into the same subtree) that we'd like to keep if possible (even if it's
not a big deal).

So I'd suggest something where -L only dereferences the symlink if it
goes outside of the list of paths passed to git-archive, and -LL (or -L
-L) dereferences anything. Of course this would only make sense if the
symlinks resolve to something that is tracked :)

For now we git archive the whole repository, use tar xh; rm what we
don't like, reset the symlinks we want to keep, and retar, which is kind
of counterproductive :)


-- 
·O·  Pierre Habouzit
··O                                                madcoder@debian.org
OOO                                                http://www.madism.org

[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply

* [PATCH v3 4/4] Change current mailmap usage to do matching on both name and email of author/committer.
From: Marius Storm-Olsen @ 2009-02-02 14:32 UTC (permalink / raw)
  To: git; +Cc: gitster, Marius Storm-Olsen
In-Reply-To: <cover.1233584536.git.marius@trolltech.com>


Signed-off-by: Marius Storm-Olsen <marius@trolltech.com>
---
 Documentation/pretty-formats.txt |    2 +
 builtin-blame.c                  |   50 +++++++++++++--------
 builtin-shortlog.c               |   22 +++++++--
 pretty.c                         |   57 ++++++++++++-----------
 t/t4203-mailmap.sh               |   93 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 173 insertions(+), 51 deletions(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 3d87d3e..28808b7 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -103,6 +103,7 @@ The placeholders are:
 - '%an': author name
 - '%aN': author name (respecting .mailmap)
 - '%ae': author email
+- '%aE': author email (respecting .mailmap)
 - '%ad': author date (format respects --date= option)
 - '%aD': author date, RFC2822 style
 - '%ar': author date, relative
@@ -111,6 +112,7 @@ The placeholders are:
 - '%cn': committer name
 - '%cN': committer name (respecting .mailmap)
 - '%ce': committer email
+- '%cE': committer email (respecting .mailmap)
 - '%cd': committer date
 - '%cD': committer date, RFC2822 style
 - '%cr': committer date, relative
diff --git a/builtin-blame.c b/builtin-blame.c
index 19edcf3..f3be9fa 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -1263,11 +1263,12 @@ struct commit_info
  * Parse author/committer line in the commit object buffer
  */
 static void get_ac_line(const char *inbuf, const char *what,
-			int bufsz, char *person, const char **mail,
+			int person_len, char *person,
+			int mail_len, char *mail,
 			unsigned long *time, const char **tz)
 {
 	int len, tzlen, maillen;
-	char *tmp, *endp, *timepos;
+	char *tmp, *endp, *timepos, *mailpos;
 
 	tmp = strstr(inbuf, what);
 	if (!tmp)
@@ -1278,10 +1279,11 @@ static void get_ac_line(const char *inbuf, const char *what,
 		len = strlen(tmp);
 	else
 		len = endp - tmp;
-	if (bufsz <= len) {
+	if (person_len <= len) {
 	error_out:
 		/* Ugh */
-		*mail = *tz = "(unknown)";
+		*tz = "(unknown)";
+		strcpy(mail, *tz);
 		*time = 0;
 		return;
 	}
@@ -1304,9 +1306,10 @@ static void get_ac_line(const char *inbuf, const char *what,
 	*tmp = 0;
 	while (*tmp != ' ')
 		tmp--;
-	*mail = tmp + 1;
+	mailpos = tmp + 1;
 	*tmp = 0;
 	maillen = timepos - tmp;
+	memcpy(mail, mailpos, maillen);
 
 	if (!mailmap.nr)
 		return;
@@ -1315,20 +1318,23 @@ static void get_ac_line(const char *inbuf, const char *what,
 	 * mailmap expansion may make the name longer.
 	 * make room by pushing stuff down.
 	 */
-	tmp = person + bufsz - (tzlen + 1);
+	tmp = person + person_len - (tzlen + 1);
 	memmove(tmp, *tz, tzlen);
 	tmp[tzlen] = 0;
 	*tz = tmp;
 
-	tmp = tmp - (maillen + 1);
-	memmove(tmp, *mail, maillen);
-	tmp[maillen] = 0;
-	*mail = tmp;
-
 	/*
-	 * Now, convert e-mail using mailmap
+	 * Now, convert both name and e-mail using mailmap
 	 */
-	map_email(&mailmap, tmp + 1, person, tmp-person-1);
+	if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+		/* Add a trailing '>' to email, since map_user returns plain emails
+		   Note: It already has '<', since we replace from mail+1 */
+		mailpos = memchr(mail, '\0', mail_len);
+		if (mailpos && mailpos-mail < mail_len - 1) {
+			*mailpos = '>';
+			*(mailpos+1) = '\0';
+		}
+	}
 }
 
 static void get_commit_info(struct commit *commit,
@@ -1337,8 +1343,10 @@ static void get_commit_info(struct commit *commit,
 {
 	int len;
 	char *tmp, *endp, *reencoded, *message;
-	static char author_buf[1024];
-	static char committer_buf[1024];
+	static char author_name[1024];
+	static char author_mail[1024];
+	static char committer_name[1024];
+	static char committer_mail[1024];
 	static char summary_buf[1024];
 
 	/*
@@ -1356,9 +1364,11 @@ static void get_commit_info(struct commit *commit,
 	}
 	reencoded = reencode_commit_message(commit, NULL);
 	message   = reencoded ? reencoded : commit->buffer;
-	ret->author = author_buf;
+	ret->author = author_name;
+	ret->author_mail = author_mail;
 	get_ac_line(message, "\nauthor ",
-		    sizeof(author_buf), author_buf, &ret->author_mail,
+		    sizeof(author_name), author_name,
+		    sizeof(author_mail), author_mail,
 		    &ret->author_time, &ret->author_tz);
 
 	if (!detailed) {
@@ -1366,9 +1376,11 @@ static void get_commit_info(struct commit *commit,
 		return;
 	}
 
-	ret->committer = committer_buf;
+	ret->committer = committer_name;
+	ret->committer_mail = committer_mail;
 	get_ac_line(message, "\ncommitter ",
-		    sizeof(committer_buf), committer_buf, &ret->committer_mail,
+		    sizeof(committer_name), committer_name,
+		    sizeof(committer_mail), committer_mail,
 		    &ret->committer_time, &ret->committer_tz);
 
 	ret->summary = summary_buf;
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
index 314b6bc..badd912 100644
--- a/builtin-shortlog.c
+++ b/builtin-shortlog.c
@@ -40,6 +40,7 @@ static void insert_one_record(struct shortlog *log,
 	char *buffer, *p;
 	struct string_list_item *item;
 	char namebuf[1024];
+	char emailbuf[1024];
 	size_t len;
 	const char *eol;
 	const char *boemail, *eoemail;
@@ -51,7 +52,19 @@ static void insert_one_record(struct shortlog *log,
 	eoemail = strchr(boemail, '>');
 	if (!eoemail)
 		return;
-	if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+
+	/* copy author name to namebuf, to support matching on both name and email */
+	memcpy(namebuf, author, boemail - author);
+	len = boemail - author;
+	while(len > 0 && isspace(namebuf[len-1]))
+		len--;
+	namebuf[len] = 0;
+
+	/* copy email name to emailbuf, to allow email replacement as well */
+	memcpy(emailbuf, boemail+1, eoemail - boemail);
+	emailbuf[eoemail - boemail - 1] = 0;
+
+	if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) {
 		while (author < boemail && isspace(*author))
 			author++;
 		for (len = 0;
@@ -67,8 +80,8 @@ static void insert_one_record(struct shortlog *log,
 
 	if (log->email) {
 		size_t room = sizeof(namebuf) - len - 1;
-		int maillen = eoemail - boemail + 1;
-		snprintf(namebuf + len, room, " %.*s", maillen, boemail);
+		int maillen = strlen(emailbuf);
+		snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf);
 	}
 
 	item = string_list_insert(namebuf, &log->list);
@@ -321,6 +334,5 @@ void shortlog_output(struct shortlog *log)
 
 	log->list.strdup_strings = 1;
 	string_list_clear(&log->list, 1);
-	log->mailmap.strdup_strings = 1;
-	string_list_clear(&log->mailmap, 1);
+	clear_mailmap(&log->mailmap);
 }
diff --git a/pretty.c b/pretty.c
index 9e03d6a..29f81c3 100644
--- a/pretty.c
+++ b/pretty.c
@@ -305,23 +305,14 @@ static char *logmsg_reencode(const struct commit *commit,
 	return out;
 }
 
-static int mailmap_name(struct strbuf *sb, const char *email)
+static int mailmap_name(char *email, int email_len, char *name, int name_len)
 {
 	static struct string_list *mail_map;
-	char buffer[1024];
-
 	if (!mail_map) {
 		mail_map = xcalloc(1, sizeof(*mail_map));
 		read_mailmap(mail_map, NULL);
 	}
-
-	if (!mail_map->nr)
-		return -1;
-
-	if (!map_email(mail_map, email, buffer, sizeof(buffer)))
-		return -1;
-	strbuf_addstr(sb, buffer);
-	return 0;
+	return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
 }
 
 static size_t format_person_part(struct strbuf *sb, char part,
@@ -332,6 +323,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
 	int start, end, tz = 0;
 	unsigned long date = 0;
 	char *ep;
+	const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
+	char person_name[1024];
+	char person_mail[1024];
 
 	/* advance 'end' to point to email start delimiter */
 	for (end = 0; end < len && msg[end] != '<'; end++)
@@ -345,25 +339,34 @@ static size_t format_person_part(struct strbuf *sb, char part,
 	if (end >= len - 2)
 		goto skip;
 
+	/* Seek for both name and email part */
+	name_start = msg;
+	name_end = msg+end;
+	while (name_end > name_start && isspace(*(name_end-1)))
+		name_end--;
+	mail_start = msg+end+1;
+	mail_end = mail_start;
+	while (mail_end < msg_end && *mail_end != '>')
+		mail_end++;
+	if (mail_end == msg_end)
+		goto skip;
+	end = mail_end-msg;
+
+	if (part == 'N' || part == 'E') { /* mailmap lookup */
+		strlcpy(person_name, name_start, name_end-name_start+1);
+		strlcpy(person_mail, mail_start, mail_end-mail_start+1);
+		mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
+		name_start = person_name;
+		name_end = name_start + strlen(person_name);
+		mail_start = person_mail;
+		mail_end = mail_start +  strlen(person_mail);
+	}
 	if (part == 'n' || part == 'N') {	/* name */
-		while (end > 0 && isspace(msg[end - 1]))
-			end--;
-		if (part != 'N' || !msg[end] || !msg[end + 1] ||
-		    mailmap_name(sb, msg + end + 2) < 0)
-			strbuf_add(sb, msg, end);
+		strbuf_add(sb, name_start, name_end-name_start);
 		return placeholder_len;
 	}
-	start = ++end; /* save email start position */
-
-	/* advance 'end' to point to email end delimiter */
-	for ( ; end < len && msg[end] != '>'; end++)
-		; /* do nothing */
-
-	if (end >= len)
-		goto skip;
-
-	if (part == 'e') {	/* email */
-		strbuf_add(sb, msg + start, end - start);
+	if (part == 'e' || part == 'E') {	/* email */
+		strbuf_add(sb, mail_start, mail_end-mail_start);
 		return placeholder_len;
 	}
 
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
index 2eded8e..798348f 100755
--- a/t/t4203-mailmap.sh
+++ b/t/t4203-mailmap.sh
@@ -106,4 +106,97 @@ test_expect_success 'No mailmap files, but configured' '
 	test_cmp expect actual
 '
 
+# Extended mailmap configurations should give us the following output for shortlog
+cat >expect <<\EOF
+A U Thor <author@example.com> (1):
+      initial
+
+Other Author <other@author.xx> (2):
+      third
+      fourth
+
+Santa Claus <santa.claus@northpole.xx> (2):
+      fifth
+      sixth
+
+Some Dude <some@dude.xx> (1):
+      second
+
+EOF
+
+test_expect_success 'Shortlog output (complex mapping)' '
+	echo three >>one &&
+	git add one &&
+	test_tick &&
+	git commit --author "nick2 <bugs@company.xx>" -m third &&
+
+	echo four >>one &&
+	git add one &&
+	test_tick &&
+	git commit --author "nick2 <nick2@company.xx>" -m fourth &&
+
+	echo five >>one &&
+	git add one &&
+	test_tick &&
+	git commit --author "santa <me@company.xx>" -m fifth &&
+
+	echo six >>one &&
+	git add one &&
+	test_tick &&
+	git commit --author "claus <me@company.xx>" -m sixth &&
+
+	mkdir internal_mailmap &&
+	echo "Committed <committer@example.com>" > internal_mailmap/.mailmap &&
+	echo "Some Dude <some@dude.xx>         nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+	echo "Other Author <other@author.xx>   nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+	echo "Other Author <other@author.xx>         <nick2@company.xx>" >> internal_mailmap/.mailmap &&
+	echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+	echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+
+	git shortlog -e >actual &&
+	test_cmp expect actual
+
+'
+
+#  # git log with --pretty format which uses the name and email mailmap placemarkers
+#  cat >expect <<\EOF
+#  Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+#  Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+#  
+#  Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+#  Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+#  
+#  Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx>
+#  Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+#  
+#  Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx>
+#  Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+#  
+#  Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx>
+#  Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+#  
+#  Author A U Thor <author@example.com> maps to A U Thor <author@example.com>
+#  Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+#  EOF
+#  
+#  test_expect_success 'Log output (complex mapping)' '
+#  	git log --pretty=format:"Author %an <%ae> maps to %aN <%aE>%nCommitter %cn <%ce> maps to %cN <%cE>%n" >actual &&
+#  	test_cmp expect actual
+#  '
+#  
+#  # git blame
+#  cat >expect <<\EOF
+#  ^3a2fdcb (A U Thor     2005-04-07 15:13:13 -0700 1) one
+#  e0f60492 (Some Dude    2005-04-07 15:14:13 -0700 2) two
+#  4db1a3df (Other Author 2005-04-07 15:15:13 -0700 3) three
+#  e3718380 (Other Author 2005-04-07 15:16:13 -0700 4) four
+#  25e4bf3d (Santa Claus  2005-04-07 15:17:13 -0700 5) five
+#  17c39712 (Santa Claus  2005-04-07 15:18:13 -0700 6) six
+#  EOF
+#  
+#  test_expect_success 'Blame output (complex mapping)' '
+#  	git blame one >actual &&
+#  	test_cmp expect actual
+#  '
+#  
 test_done
-- 
1.6.1.2.257.g84fd75

^ permalink raw reply related

* [PATCH v3 3/4] Add map_user() and clear_mailmap() to mailmap
From: Marius Storm-Olsen @ 2009-02-02 14:32 UTC (permalink / raw)
  To: git; +Cc: gitster, Marius Storm-Olsen
In-Reply-To: <cover.1233584536.git.marius@trolltech.com>

map_user() allows to lookup and replace both email and
name of a user, based on a new style mailmap file.
The possible mailmap definitions now are:
  proper_name <commit_email>                             // Old style
  proper_name <proper_email> <commit_email>              // New style
  proper_name <proper_email> commit_name <commit_email>  // New style

map_email() operates the same as before, with the
exception that it also will to try to match on a name
passed in through the name return buffer.

clear_mailmap() is needed to now clear the more complex
mailmap structure.

Signed-off-by: Marius Storm-Olsen <marius@trolltech.com>
---
 Documentation/git-shortlog.txt |   58 +++++++++---
 mailmap.c                      |  198 ++++++++++++++++++++++++++++++++++++----
 mailmap.h                      |    4 +
 3 files changed, 227 insertions(+), 33 deletions(-)

diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index cacbeea..d64dabf 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -50,20 +50,29 @@ FILES
 
 If a file `.mailmap` exists at the toplevel of the repository, or at the
 location pointed to by the log.mailmap configuration option,
-it is used to map an author email address to a canonical real name. This
-can be used to coalesce together commits by the same person where their
-name was spelled differently (whether with the same email address or
-not).
-
-Each line in the file consists, in this order, of the canonical real name
-of an author, whitespace, and an email address (enclosed by '<' and '>')
-to map to the name. Use hash '#' for comments, either on their own line,
-or after the email address.
-
-A canonical name may appear in more than one line, associated with
-different email addresses, but it doesn't make sense for a given address
-to appear more than once (if that happens, a later line overrides the
-earlier ones).
+it is used to map author and committer names and email addresses to
+canonical real names and email addresses.
+This can be used to coalesce together commits by the same person where
+their name and/or email address was spelled differently.
+
+In the simple form, each line in the file consists of the canonical real name
+of an author, whitespace, and an email address used in the commit
+(enclosed by '<' and '>') to map to the name. Thus, looks like this
+--
+	Proper Name <commit@email.xx>
+--
+
+The more complex forms are
+--
+	Proper Name <proper@email.xx> <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a commit
+matching the specified commit email address. And
+--
+	Proper Name <proper@email.xx> Commit Name <commit@email.xx>
+--
+which allows mailmap to replace both the name and the email of a commit
+matching the specified commit name and email address.
 
 So, for example, if your history contains commits by two authors, Jane
 and Joe, whose names appear in the repository under several forms:
@@ -86,6 +95,27 @@ Jane Doe <jane@desktop.(none)>
 Joe R. Developer <joe@random.com>
 ------------
 
+Now, suppose your repository contains commits from the following authors:
+
+------------
+nick1 <bugs@company.xx>
+nick2 <bugs@company.xx>
+nick2 <nick2@company.xx>
+santa <me@company.xx>
+claus <me@company.xx>
+------------
+
+Then, you might want a `.mailmap` file looking like:
+------------
+Some Dude <some@dude.xx>         nick1 <bugs@company.xx>
+Other Author <other@author.xx>   nick2 <bugs@company.xx>
+Other Author <other@author.xx>         <nick2@company.xx>
+Santa Claus <santa.claus@northpole.xx> <me@company.xx>
+------------
+
+Use hash '#' for comments, either on their own line, or after the email address.
+
+
 Author
 ------
 Written by Jeff Garzik <jgarzik@pobox.com>
diff --git a/mailmap.c b/mailmap.c
index 5aaee91..f593ff0 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -2,7 +2,87 @@
 #include "string-list.h"
 #include "mailmap.h"
 
+#define DEBUG_MAILMAP 0
+#if DEBUG_MAILMAP
+#define debug_mm(...) fprintf(stderr, __VA_ARGS__)
+#else
+inline void debug_mm(const char *format, ...) {}
+#endif
+
 const char *git_log_mailmap;
+
+struct mailmap_info {
+	char *name;
+	char *email;
+};
+
+struct mailmap_entry {
+	/* name and email for the simple mail-only case */
+	char *name;
+	char *email;
+
+	/* name and email for the complex mail and name matching case */
+	struct string_list namemap;
+};
+
+void free_mailmap_info(void *p, const char *s)
+{
+	struct mailmap_info *mi = (struct mailmap_info *)p;
+	debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email);
+	free(mi->name);
+	free(mi->email);
+}
+
+void free_mailmap_entry(void *p, const char *s)
+{
+	struct mailmap_entry *me = (struct mailmap_entry *)p;
+	debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr);
+	debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email);
+	free(me->name);
+	free(me->email);
+
+	me->namemap.strdup_strings = 1;
+	string_list_clear_func(&me->namemap, free_mailmap_info);
+}
+
+void add_mapping(struct string_list *map,
+		 char *name1, char *email1, char *name2, char *email2)
+{
+	struct mailmap_entry *me;
+	int index = string_list_find_insert_index(map, email1, 1);
+	if (index < 0) {
+		/* mailmap entry exists, invert index value */
+		index = -1 - index;
+	} else {
+		/* create mailmap entry */
+		struct string_list_item *item = string_list_insert_at_index(index, email1, map);
+		item->util = xmalloc(sizeof(struct mailmap_entry));
+		memset(item->util, 0, sizeof(struct mailmap_entry));
+	}
+	me = (struct mailmap_entry *)map->items[index].util;
+
+	if (name2 == NULL) {
+		debug_mm("mailmap: adding (simple) entry for %s at index %d\n", email1, index);
+		/* Replace current name and new email for simple entry */
+		free(me->name);
+		free(me->email);
+		me->name = xstrdup(name1);
+		if (email2)
+			me->email = xstrdup(email2);
+	} else {
+		struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));
+		debug_mm("mailmap: adding (complex) entry for %s at index %d\n", email1, index);
+		mi->name = xstrdup(name2);
+		if (email2)
+			mi->email = xstrdup(email2);
+		string_list_insert(name1, &me->namemap)->util = mi;
+	}
+
+	debug_mm("mailmap:  '%s' <%s> -> '%s' <%s>\n",
+		 name2 ? name1 : "", email1,
+		 name2 ? name2 : name1, email2 ? email2 : "");
+}
+
 static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
 {
 	char buffer[1024];
@@ -11,8 +91,8 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch
 	if (f == NULL)
 		return 1;
 	while (fgets(buffer, sizeof(buffer), f) != NULL) {
-		char *end_of_name, *left_bracket, *right_bracket;
-		char *name, *email;
+		char *end_of_name, *left_bracket1, *right_bracket1, *left_bracket2, *right_bracket2;
+		char *name1, *email1, *name2, *email2;
 		int i;
 		if (buffer[0] == '#') {
 			static const char abbrev[] = "# repo-abbrev:";
@@ -37,25 +117,65 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch
 			}
 			continue;
 		}
-		if ((left_bracket = strchr(buffer, '<')) == NULL)
+		/* Locate name and email */
+		if ((left_bracket1 = strchr(buffer, '<')) == NULL)
 			continue;
-		if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
+		if ((right_bracket1 = strchr(left_bracket1 + 1, '>')) == NULL)
 			continue;
-		if (right_bracket == left_bracket + 1)
+		if (right_bracket1 == left_bracket1 + 1)
 			continue;
-		for (end_of_name = left_bracket;
+		for (end_of_name = left_bracket1;
 		     end_of_name != buffer && isspace(end_of_name[-1]);
 		     end_of_name--)
 			; /* keep on looking */
 		if (end_of_name == buffer)
 			continue;
-		name = xmalloc(end_of_name - buffer + 1);
-		strlcpy(name, buffer, end_of_name - buffer + 1);
-		email = xmalloc(right_bracket - left_bracket);
-		for (i = 0; i < right_bracket - left_bracket - 1; i++)
-			email[i] = tolower(left_bracket[i + 1]);
-		email[right_bracket - left_bracket - 1] = '\0';
-		string_list_insert(email, map)->util = name;
+		name1 = xmalloc(end_of_name - buffer + 1);
+		strlcpy(name1, buffer, end_of_name - buffer + 1);
+		email1 = xmalloc(right_bracket1 - left_bracket1);
+		for (i = 0; i < right_bracket1 - left_bracket1 - 1; i++)
+			email1[i] = tolower(left_bracket1[i + 1]);
+		email1[right_bracket1 - left_bracket1 - 1] = '\0';
+
+		/* Locate 2nd name and email. Possible mappings in mailmap file are:
+		 *   proper_name <commit_email>
+		 *   proper_name <proper_email> <commit_email>
+		 *   proper_name <proper_email> commit_name <commit_email>
+		 */
+		do {
+			email2 = name2 = 0;
+			right_bracket1 += 1;
+			if ((left_bracket2 = strchr(right_bracket1, '<')) == NULL)
+				continue;
+			if ((right_bracket2 = strchr(left_bracket2 + 1, '>')) == NULL)
+				continue;
+			if (right_bracket2 == left_bracket2 + 1)
+				continue;
+			for (end_of_name = left_bracket2;
+			     end_of_name != right_bracket1 && isspace(end_of_name[-1]);
+			     end_of_name--)
+				; /* keep on looking for name end */
+			for (;
+			     end_of_name != right_bracket1 && isspace(right_bracket1[0]);
+			     right_bracket1++)
+				; /* keep on looking for name start */
+			if (end_of_name != right_bracket1) {
+				name2 = xmalloc(end_of_name - right_bracket1 + 1);
+				strlcpy(name2, right_bracket1, end_of_name - right_bracket1 + 1);
+				char *tmp = name1;
+				name1 = name2;
+				name2 = tmp;
+			}
+			email2 = xmalloc(right_bracket2 - left_bracket2);
+			for (i = 0; i < right_bracket2 - left_bracket2 - 1; i++)
+				email2[i] = tolower(left_bracket2[i + 1]);
+			email2[right_bracket2 - left_bracket2 - 1] = '\0';
+			char *tmp = email1;
+			email1 = email2;
+			email2 = tmp;
+		} while(0);
+
+		add_mapping(map, name1, email1, name2, email2);
 	}
 	fclose(f);
 	return 0;
@@ -68,17 +188,31 @@ int read_mailmap(struct string_list *map, char **repo_abbrev)
 	       read_single_mailmap(map, git_log_mailmap, repo_abbrev) > 1;
 }
 
-int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+void clear_mailmap(struct string_list *map)
+{
+	debug_mm("mailmap: clearing %d entries...\n", map->nr);
+	map->strdup_strings = 1;
+	string_list_clear_func(map, free_mailmap_entry);
+	debug_mm("mailmap: cleared\n");
+}
+
+int map_user(struct string_list *map,
+	     char *email, int maxlen_email, char *name, int maxlen_name)
 {
 	char *p;
 	struct string_list_item *item;
+	struct mailmap_entry *me;
 	char buf[1024], *mailbuf;
 	int i;
 
-	/* autocomplete common developers */
+	/* figure out space requirement for email */
 	p = strchr(email, '>');
-	if (!p)
-		return 0;
+	if (!p) {
+		/* email passed in might not be wrapped in <>, but end with a \0 */
+		p = memchr(email, '\0', maxlen_email);
+		if (p == 0)
+			return 0;
+	}
 	if (p - email + 1 < sizeof(buf))
 		mailbuf = buf;
 	else
@@ -88,13 +222,39 @@ int map_email(struct string_list *map, const char *email, char *name, int maxlen
 	for (i = 0; i < p - email; i++)
 		mailbuf[i] = tolower(email[i]);
 	mailbuf[i] = 0;
+
+	debug_mm("map_user: map '%s' <%s>\n", name, mailbuf);
 	item = string_list_lookup(mailbuf, map);
+	if (item != NULL) {
+		me = (struct mailmap_entry *)item->util;
+		if (me->namemap.nr) {
+			/* The item has multiple items, so we'll look up on name too */
+			/* If the name is not found, we choose the simple entry      */
+			struct string_list_item *subitem = string_list_lookup(name, &me->namemap);
+			if (subitem)
+				item = subitem;
+		}
+	}
 	if (mailbuf != buf)
 		free(mailbuf);
 	if (item != NULL) {
-		const char *realname = (const char *)item->util;
-		strlcpy(name, realname, maxlen);
+		struct mailmap_info *mi = (struct mailmap_info *)item->util;
+		if (mi->email == NULL && mi->name == NULL) {
+			debug_mm("map_user:  -- (no simple mapping)\n");
+			return 0;
+		}
+		if (maxlen_email && mi->email)
+			strlcpy(email, mi->email, maxlen_email);
+		if (maxlen_name && mi->name)
+			strlcpy(name, mi->name, maxlen_name);
+		debug_mm("map_user:  to '%s' <%s>\n", name, mi->email ? email : mailbuf);
 		return 1;
 	}
+	debug_mm("map_user:  --\n");
 	return 0;
 }
+
+int map_email(struct string_list *map, const char *email, char *name, int maxlen)
+{
+	return map_user(map, (char *)email, 0, name, maxlen);
+}
diff --git a/mailmap.h b/mailmap.h
index ba2ee76..4b2ca3a 100644
--- a/mailmap.h
+++ b/mailmap.h
@@ -2,6 +2,10 @@
 #define MAILMAP_H
 
 int read_mailmap(struct string_list *map, char **repo_abbrev);
+void clear_mailmap(struct string_list *map);
+
 int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
+int map_user(struct string_list *mailmap,
+	     char *email, int maxlen_email, char *name, int maxlen_name);
 
 #endif
-- 
1.6.1.2.257.g84fd75

^ permalink raw reply related

* [PATCH v3 2/4] Add find_insert_index, insert_at_index and clear_func functions to string_list
From: Marius Storm-Olsen @ 2009-02-02 14:32 UTC (permalink / raw)
  To: git; +Cc: gitster, Marius Storm-Olsen
In-Reply-To: <cover.1233584536.git.marius@trolltech.com>

string_list_find_insert_index() and string_list_insert_at_index()
enables you to see if an item is in the string_list, and to
insert at the appropriate index in the list, if not there.
This is usefull if you need to manipulate an existing item,
if present, and insert a new item if not.

Future mailmap code will use this construct to enable
complex (old_name, old_email) -> (new_name, new_email)
lookups.

The string_list_clear_func() allows to call a custom
cleanup function on each item in a string_list, which is
useful is the util member points to a complex structure.

Signed-off-by: Marius Storm-Olsen <marius@trolltech.com>
---
 string-list.c |   43 +++++++++++++++++++++++++++++++++++++++----
 string-list.h |    9 +++++++++
 2 files changed, 48 insertions(+), 4 deletions(-)

diff --git a/string-list.c b/string-list.c
index ddd83c8..15e14cf 100644
--- a/string-list.c
+++ b/string-list.c
@@ -26,10 +26,10 @@ static int get_entry_index(const struct string_list *list, const char *string,
 }
 
 /* returns -1-index if already exists */
-static int add_entry(struct string_list *list, const char *string)
+static int add_entry(int insert_at, struct string_list *list, const char *string)
 {
-	int exact_match;
-	int index = get_entry_index(list, string, &exact_match);
+	int exact_match = 0;
+	int index = insert_at != -1 ? insert_at : get_entry_index(list, string, &exact_match);
 
 	if (exact_match)
 		return -1 - index;
@@ -53,7 +53,13 @@ static int add_entry(struct string_list *list, const char *string)
 
 struct string_list_item *string_list_insert(const char *string, struct string_list *list)
 {
-	int index = add_entry(list, string);
+	return string_list_insert_at_index(-1, string, list);
+}
+
+struct string_list_item *string_list_insert_at_index(int insert_at,
+						     const char *string, struct string_list *list)
+{
+	int index = add_entry(insert_at, list, string);
 
 	if (index < 0)
 		index = -1 - index;
@@ -68,6 +74,16 @@ int string_list_has_string(const struct string_list *list, const char *string)
 	return exact_match;
 }
 
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+				  int negative_existing_index)
+{
+	int exact_match;
+	int index = get_entry_index(list, string, &exact_match);
+	if (exact_match)
+		index = -1 - (negative_existing_index ? index : 0);
+	return index;
+}
+
 struct string_list_item *string_list_lookup(const char *string, struct string_list *list)
 {
 	int exact_match, i = get_entry_index(list, string, &exact_match);
@@ -94,6 +110,25 @@ void string_list_clear(struct string_list *list, int free_util)
 	list->nr = list->alloc = 0;
 }
 
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc)
+{
+	if (list->items) {
+		int i;
+		if (clearfunc) {
+			for (i = 0; i < list->nr; i++)
+				clearfunc(list->items[i].util, list->items[i].string);
+		}
+		if (list->strdup_strings) {
+			for (i = 0; i < list->nr; i++)
+				free(list->items[i].string);
+		}
+		free(list->items);
+	}
+	list->items = NULL;
+	list->nr = list->alloc = 0;
+}
+
+
 void print_string_list(const char *text, const struct string_list *p)
 {
 	int i;
diff --git a/string-list.h b/string-list.h
index 4d6a705..d32ba05 100644
--- a/string-list.h
+++ b/string-list.h
@@ -15,9 +15,18 @@ struct string_list
 void print_string_list(const char *text, const struct string_list *p);
 void string_list_clear(struct string_list *list, int free_util);
 
+/* Use this function to call a custom clear function on each util pointer */
+/* The string associated with the util pointer is passed as the second argument */
+typedef void (*string_list_clear_func_t)(void *p, const char *str);
+void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);
+
 /* Use these functions only on sorted lists: */
 int string_list_has_string(const struct string_list *list, const char *string);
+int string_list_find_insert_index(const struct string_list *list, const char *string,
+				  int negative_existing_index);
 struct string_list_item *string_list_insert(const char *string, struct string_list *list);
+struct string_list_item *string_list_insert_at_index(int insert_at,
+						     const char *string, struct string_list *list);
 struct string_list_item *string_list_lookup(const char *string, struct string_list *list);
 
 /* Use these functions only on unsorted lists: */
-- 
1.6.1.2.257.g84fd75

^ permalink raw reply related

* [PATCH v3 1/4] Add log.mailmap as configurational option for mailmap location
From: Marius Storm-Olsen @ 2009-02-02 14:32 UTC (permalink / raw)
  To: git; +Cc: gitster, Marius Storm-Olsen
In-Reply-To: <cover.1233584536.git.marius@trolltech.com>

This allows us to augment the repo mailmap file, and to use
mailmap files elsewhere than the repository root. Meaning
that the entries in log.mailmap will override the entries
in "./.mailmap", should they match the same.

Signed-off-by: Marius Storm-Olsen <marius@trolltech.com>
---
 Documentation/config.txt       |    8 +++
 Documentation/git-shortlog.txt |    3 +-
 builtin-blame.c                |    2 +-
 builtin-shortlog.c             |    3 +-
 cache.h                        |    1 +
 config.c                       |   10 ++++
 mailmap.c                      |   12 ++++-
 mailmap.h                      |    2 +-
 pretty.c                       |    2 +-
 t/t4203-mailmap.sh             |  109 ++++++++++++++++++++++++++++++++++++++++
 10 files changed, 145 insertions(+), 7 deletions(-)
 create mode 100755 t/t4203-mailmap.sh

diff --git a/Documentation/config.txt b/Documentation/config.txt
index e2b8775..1334d36 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1012,6 +1012,14 @@ log.showroot::
 	Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which
 	normally hide the root commit will now show it. True by default.
 
+log.mailmap::
+	The location of an augmenting mailmap file. The default
+	mailmap, located in the root of the repository, is loaded
+	first, then the mailmap file pointed to by this variable.
+	The location of the mailmap file may be in a repository
+	subdirectory, or somewhere outside of the repository itself.
+	See linkgit:git-shortlog[1] and linkgit:git-blame[1].
+
 man.viewer::
 	Specify the programs that may be used to display help in the
 	'man' format. See linkgit:git-help[1].
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index 8f7c0e2..cacbeea 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -48,7 +48,8 @@ OPTIONS
 FILES
 -----
 
-If a file `.mailmap` exists at the toplevel of the repository,
+If a file `.mailmap` exists at the toplevel of the repository, or at the
+location pointed to by the log.mailmap configuration option,
 it is used to map an author email address to a canonical real name. This
 can be used to coalesce together commits by the same person where their
 name was spelled differently (whether with the same email address or
diff --git a/builtin-blame.c b/builtin-blame.c
index aae14ef..19edcf3 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -2394,7 +2394,7 @@ parse_done:
 		die("reading graft file %s failed: %s",
 		    revs_file, strerror(errno));
 
-	read_mailmap(&mailmap, ".mailmap", NULL);
+	read_mailmap(&mailmap, NULL);
 
 	if (!incremental)
 		setup_pager();
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
index 5f9f3f0..314b6bc 100644
--- a/builtin-shortlog.c
+++ b/builtin-shortlog.c
@@ -219,7 +219,7 @@ void shortlog_init(struct shortlog *log)
 {
 	memset(log, 0, sizeof(*log));
 
-	read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
+	read_mailmap(&log->mailmap, &log->common_repo_prefix);
 
 	log->list.strdup_strings = 1;
 	log->wrap = DEFAULT_WRAPLEN;
@@ -248,6 +248,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
 	struct parse_opt_ctx_t ctx;
 
 	prefix = setup_git_directory_gently(&nongit);
+	git_config(git_default_config, NULL);
 	shortlog_init(&log);
 	init_revisions(&rev, prefix);
 	parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
diff --git a/cache.h b/cache.h
index 45e713e..3eef7ea 100644
--- a/cache.h
+++ b/cache.h
@@ -867,6 +867,7 @@ extern int user_ident_explicitly_given;
 
 extern const char *git_commit_encoding;
 extern const char *git_log_output_encoding;
+extern const char *git_log_mailmap;
 
 /* IO helper functions */
 extern void maybe_flush_or_die(FILE *, const char *);
diff --git a/config.c b/config.c
index 790405a..9ebcbbe 100644
--- a/config.c
+++ b/config.c
@@ -565,6 +565,13 @@ static int git_default_branch_config(const char *var, const char *value)
 	return 0;
 }
 
+static int git_default_log_config(const char *var, const char *value)
+{
+	if (!strcmp(var, "log.mailmap"))
+		return git_config_string(&git_log_mailmap, var, value);
+	return 0;
+}
+
 int git_default_config(const char *var, const char *value, void *dummy)
 {
 	if (!prefixcmp(var, "core."))
@@ -579,6 +586,9 @@ int git_default_config(const char *var, const char *value, void *dummy)
 	if (!prefixcmp(var, "branch."))
 		return git_default_branch_config(var, value);
 
+	if (!prefixcmp(var, "log."))
+		return git_default_log_config(var, value);
+
 	if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
 		pager_use_color = git_config_bool(var,value);
 		return 0;
diff --git a/mailmap.c b/mailmap.c
index 88fc6f3..5aaee91 100644
--- a/mailmap.c
+++ b/mailmap.c
@@ -2,10 +2,11 @@
 #include "string-list.h"
 #include "mailmap.h"
 
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
+const char *git_log_mailmap;
+static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev)
 {
 	char buffer[1024];
-	FILE *f = fopen(filename, "r");
+	FILE *f = (filename == NULL ? NULL : fopen(filename, "r"));
 
 	if (f == NULL)
 		return 1;
@@ -60,6 +61,13 @@ int read_mailmap(struct string_list *map, const char *filename, char **repo_abbr
 	return 0;
 }
 
+int read_mailmap(struct string_list *map, char **repo_abbrev)
+{
+	/* each failure returns 1, so >1 means both calls failed */
+	return read_single_mailmap(map, ".mailmap", repo_abbrev) +
+	       read_single_mailmap(map, git_log_mailmap, repo_abbrev) > 1;
+}
+
 int map_email(struct string_list *map, const char *email, char *name, int maxlen)
 {
 	char *p;
diff --git a/mailmap.h b/mailmap.h
index 6e48f83..ba2ee76 100644
--- a/mailmap.h
+++ b/mailmap.h
@@ -1,7 +1,7 @@
 #ifndef MAILMAP_H
 #define MAILMAP_H
 
-int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev);
+int read_mailmap(struct string_list *map, char **repo_abbrev);
 int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen);
 
 #endif
diff --git a/pretty.c b/pretty.c
index cc460b5..9e03d6a 100644
--- a/pretty.c
+++ b/pretty.c
@@ -312,7 +312,7 @@ static int mailmap_name(struct strbuf *sb, const char *email)
 
 	if (!mail_map) {
 		mail_map = xcalloc(1, sizeof(*mail_map));
-		read_mailmap(mail_map, ".mailmap", NULL);
+		read_mailmap(mail_map, NULL);
 	}
 
 	if (!mail_map->nr)
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
new file mode 100755
index 0000000..2eded8e
--- /dev/null
+++ b/t/t4203-mailmap.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+test_description='.mailmap configurations'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	echo one >one &&
+	git add one &&
+	test_tick &&
+	git commit -m initial &&
+	echo two >>one &&
+	git add one &&
+	git commit --author "nick1 <bugs@company.xx>" -m second
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'No mailmap' '
+	git shortlog >actual &&
+	test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'default .mailmap' '
+	echo "Repo Guy <author@example.com>" > .mailmap &&
+	git shortlog >actual &&
+	test_cmp expect actual
+'
+
+# Using a mailmap file in a subdirectory of the repo here, but
+# could just as well have been a file outside of the repository
+cat >expect <<\EOF
+Internal Guy (1):
+      second
+
+Repo Guy (1):
+      initial
+
+EOF
+test_expect_success 'log.mailmap set' '
+	mkdir internal_mailmap &&
+	echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap &&
+	git config log.mailmap internal_mailmap/.mailmap &&
+	git shortlog >actual &&
+	test_cmp expect actual
+'
+
+cat >expect <<\EOF
+External Guy (1):
+      initial
+
+Internal Guy (1):
+      second
+
+EOF
+test_expect_success 'log.mailmap override' '
+	echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap &&
+	git config log.mailmap internal_mailmap/.mailmap &&
+	git shortlog >actual &&
+	test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'log.mailmap file non-existant' '
+	rm internal_mailmap/.mailmap &&
+	rmdir internal_mailmap &&
+	git shortlog >actual &&
+	test_cmp expect actual
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+test_expect_success 'No mailmap files, but configured' '
+	rm .mailmap &&
+	git shortlog >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
1.6.1.2.257.g84fd75

^ permalink raw reply related

* [PATCH v3 0/4] Extend mailmap functionality
From: Marius Storm-Olsen @ 2009-02-02 14:32 UTC (permalink / raw)
  To: git; +Cc: gitster, Marius Storm-Olsen

  v3:
  ---
  * Make log.mailmap augment repo "/.mailmap" rather than override
  * Remove second argument of read_mailmap(<map>, <file>, <abbrev>);
  * Wrap commit messages within column 70
  v2:
  ---
  * Folded in documentation fixup from patch 4 into patch 3.


This patch series extends the mailmap functionality to:
  1) Allow the mailmap file in any location (also outside repo)
  2) Enable mailmap to match on both Name and Email

So, why would this be a good thing?

2) Lets you replace both name and email of an author/committer, based
on a name and/or email. So, should you have done commits with faulty
address, or if an old email simply isn't valid anymore, you can add
a mapping for that to replace it. So, the old style mapping is
    Proper Name <commit@email.xx>

while this patch series adds support for
    Proper Name <proper@email.xx> <commit@email.xx>
    Proper Name <proper@email.xx> Commit Name <commit@email.xx>

1) Lets you keep a private mailmap file, which is not distributed with
your repository.


This extended mapping is necessary when a company wants to have their
repositories open to the public, but needs to protect the identities
of the developers. It enables you to only show nicks and standardized
emails, like 'Dev123 <bugs@company.xx>' in the public repo, but by
using an private mailmap file, map the name back to
'John Doe <john.doe@company.xx>' inside the company.


Patch serie applies cleanly on master branch, and test run shows no
regressions.

Marius Storm-Olsen (4):
  Add log.mailmap as configurational option for mailmap location
  Add find_insert_index, insert_at_index and clear_func functions to
    string_list
  Add map_user() and clear_mailmap() to mailmap
  Change current mailmap usage to do matching on both name and email of
    author/committer.

 Documentation/config.txt         |    8 ++
 Documentation/git-shortlog.txt   |   61 ++++++++---
 Documentation/pretty-formats.txt |    2 +
 builtin-blame.c                  |   52 ++++++----
 builtin-shortlog.c               |   25 ++++-
 cache.h                          |    1 +
 config.c                         |   10 ++
 mailmap.c                        |  210 ++++++++++++++++++++++++++++++++++----
 mailmap.h                        |    6 +-
 pretty.c                         |   59 ++++++-----
 string-list.c                    |   43 +++++++-
 string-list.h                    |    9 ++
 t/t4203-mailmap.sh               |  202 ++++++++++++++++++++++++++++++++++++
 13 files changed, 593 insertions(+), 95 deletions(-)
 create mode 100755 t/t4203-mailmap.sh

^ permalink raw reply

* cvs2git migration - cloning CVS repository
From: Rostislav Svoboda @ 2009-02-02 14:24 UTC (permalink / raw)
  To: git

Hi everyone

I'd like to clone a CVS repository to a local machine in order to
prepare it for a migration to git. I tried:

$git cvsimport -p x -v -d
:pserver:mylogin@cvs-server:/cvs/cvsrepository cvsmodule
AuthReply: I HATE YOU
$git cvsimport -p x -v -d
:pserver:anonymous@cvs-server:/cvs/cvsrepository cvsmodule
AuthReply: E Fatal error, aborting.

I haven't found much info concerning the 'I HATE YOU'. So I'd like to
ask you if I'm right:
(I can't talk about this with my cvs-server administrator. He's a kind of dumb)

- git cvsimport works only using 'anonymous' account. So my real login
name won't work?
- 'AuthReply: E Fatal error, aborting.' means that the cvs-server does
not accept anonymous connections?

Is there any other way how to get the cvs repository to my local machine?

thx a lot

Bost

^ permalink raw reply

* Re: [PATCH] push: Learn to set up branch tracking with '--track'
From: Johannes Schindelin @ 2009-02-02 13:52 UTC (permalink / raw)
  To: Jeff King; +Cc: git, gitster
In-Reply-To: <20090202131611.GB8487@sigio.peff.net>

Hi,

On Mon, 2 Feb 2009, Jeff King wrote:

> It looks like the consensus is to add a branch.master config section 
> even when cloning an empty repo. And that should address my concern in 
> the 99% of cases where people use the default "master" setup. Which kind 
> of takes away the main use case for this topic.

>From where I sit, the main use would have been:

	# <hackhackhack>
	$ git init
	$ git add .
	$ git commit -m initial\ revision
	# <hackhackhack>
	# <create a repository on repo.or.cz>
	$ git remote add origin $URL
	$ git push -t origin master

BTW I always wondered if it would make sense to introduce "git commit 
--init" for the first 3 Git commands.  I use them way too often.

Ah, well, I will install an alias on all of my machines.

Ciao,
Dscho

^ permalink raw reply

* [PATCH] Force gitk as the forground process when it starts up on Mac  OS X
From: Gisle Aas @ 2009-02-02 13:36 UTC (permalink / raw)
  To: git

It annoys me that gitk always start out in the background on Mac OS X.
 This patch fixes this for those that have the tclCarbonProcess
package installed.  Unfortunately that package is not shipped by
default from Apple, but it's part of ActiveTcl.

--Gisle


>From 272c7f9b6a2bcdf48e4141b9dc5af445fa69a27b Mon Sep 17 00:00:00 2001
From: Gisle Aas <gisle@aas.no>
Date: Mon, 2 Feb 2009 14:27:20 +0100
Subject: [PATCH] Force gitk as the forground process when it starts up
on Mac OS X

Signed-off-by: Gisle Aas <gisle@aas.no>
---
 gitk-git/gitk |    6 ++++++
 1 files changed, 6 insertions(+), 0 deletions(-)

diff --git a/gitk-git/gitk b/gitk-git/gitk
index dc2a439..e0c11f7 100644
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -10918,5 +10918,11 @@ if {[info exists permviews]} {
 if {[tk windowingsystem] eq "win32"} {
     focus -force .
 }
+if {[tk windowingsystem] eq "aqua"} {
+    catch {
+        package require tclCarbonProcesses
+        carbon::setFrontProcess [carbon::getCurrentProcess]
+    }
+}

 getcommits {}
-- 
1.6.1.28.gc32f76

^ permalink raw reply related

* Re: [PATCH] builtin-remote: make rm operation safer in mirrored  repository
From: Jay Soffian @ 2009-02-02 13:36 UTC (permalink / raw)
  To: Jeff King; +Cc: git
In-Reply-To: <20090202132909.GD8487@sigio.peff.net>

On Mon, Feb 2, 2009 at 8:29 AM, Jeff King <peff@peff.net> wrote:
> In that case, your safety valve makes more
> sense.

Thank you for the review.

>>  builtin-remote.c |    7 +++++++
>>  1 files changed, 7 insertions(+), 0 deletions(-)
>
> Tests?

Yep, will resend w/tests.

j.

^ permalink raw reply

* Re: [PATCH] builtin-remote: make rm operation safer in mirrored repository
From: Jeff King @ 2009-02-02 13:29 UTC (permalink / raw)
  To: Jay Soffian; +Cc: git
In-Reply-To: <1233503309-40144-1-git-send-email-jaysoffian@gmail.com>

On Sun, Feb 01, 2009 at 10:48:29AM -0500, Jay Soffian wrote:

> "git remote rm <repo>" is happy to remove non-remote branches (and their
> reflogs). This may be okay if the repository truely is a mirror, but if the
> user had done "git remote add --mirror <repo>" by accident and was just
> undoing their mistake, then they are left in a situation that is difficult to
> recover from.
> 
> After this commit, "git remote rm" skips over non-remote branches and instead
> advises the user on how to remove such branches using "git branch -d", which
> itself has nice safety checks wrt to branch removal lacking from "git remote
> rm".

I think this is sensible. The point of "git remote rm" removing tracking
branches is just to clean up cruft in a repository that has otherwise
interesting stuff. Blowing away a mirror implies getting rid of all
refs. At which point I have to wonder why you would want to do that
versus just removing the repo entirely. I.e., the common use case for
"git remote rm" on a mirror would seem to be "oops, I did this wrong"
and not "I really want to get rid of all my refs".

So I think a safety valve is reasonable.  I wonder if would be simpler
still to just not delete _any_ refs for a mirrored remote. On the other
hand, your safety valve also protects against unusual refspecs that
might touch refs not in refs/remotes (e.g., if I didn't use
remote.foo.mirror, but I had a manually-written refspec that touched
some subset of refs/heads/). In that case, your safety valve makes more
sense.

>  builtin-remote.c |    7 +++++++
>  1 files changed, 7 insertions(+), 0 deletions(-)

Tests?

-Peff

^ permalink raw reply

* Re: [RFC PATCH] add -p: prompt for single characters
From: Jeff King @ 2009-02-02 13:19 UTC (permalink / raw)
  To: Thomas Rast; +Cc: Junio C Hamano, git, Suraj N. Kurapati
In-Reply-To: <1233520511-13061-1-git-send-email-trast@student.ethz.ch>

On Sun, Feb 01, 2009 at 09:35:11PM +0100, Thomas Rast wrote:

> I wrote this on the train today, but it turns out a similar idea was
> already around earlier:
> 
>   http://thread.gmane.org/gmane.comp.version-control.git/100146
> 
> I can't find the v4 promised there, so I assume Suraj dropped it.

It looks like you addressed the concerns I had with the original. I do
think, as Junio mentioned, that it makes sense for this to be
configurable. Because it's interactive by defintion, I don't know if we
have the same compatibility concerns that we usually do. So I'm not sure
if such a config option should default to off or on.

-Peff

^ permalink raw reply

* Re: [PATCH] push: Learn to set up branch tracking with '--track'
From: Jeff King @ 2009-02-02 13:16 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, gitster
In-Reply-To: <alpine.DEB.1.00.0901301804030.3586@pacific.mpi-cbg.de>

On Fri, Jan 30, 2009 at 06:05:30PM +0100, Johannes Schindelin wrote:

> > I think the right thing to do is:
> > 
> >   1. factor out "generic" routines from send-pack, including status
> >      output formatting and tracking ref updating
> > 
> >   2. refactor http-push to use those routines, bringing it in line with
> >      send-pack
> > 
> >   3. add --track support in the same generic way, and hook it from both
> >      transports
> 
> Now we're thinking along the same lines!

OK, good.

> > I can try to work on this, but I'm not excited about major surgery to 
> > http-push, which I don't have a working test setup for.
> 
> You don't have an apache installed?

No, though "apt-get install apache" is easy enough. I was more concerned
about wading through the mess of apache configuration to turn on webdave
support. But that is just my empty grumbling and complaining; it's not a
real stumbling block to doing this patch.

And when I wrote that, I was fully intending to pick up this topic and
work on the steps outlined above.

_But_.

It looks like the consensus is to add a branch.master config section
even when cloning an empty repo. And that should address my concern in
the 99% of cases where people use the default "master" setup. Which kind
of takes away the main use case for this topic.

So it doesn't make much sense to me to put effort into it now.  The
http-push cleanups might be nice for http-push users, but I don't
remember even seeing a single user request or complaint about it. So I
am not too keen to go cleaning up code that _I_ don't care about, and
that I am not sure anyone _else_ even cares about.

-Peff

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox